I know this might be a common question but I am really stuck at this point.
I am receiving data from 2 multiple locations and after I received from both I need to continue executing and than return that data to the calling method.
I am aware of this thread: https://stackoverflow.com/a/33204705/1820644 but it doesn't fit here actually as I need to return the data to the calling method.
For the method that blocks UI thread I can call it from AsyncTask, there is no problem. But how can I return data to the calling method that I have successfully completed execution.
This is inside my helper class
// This method should be called in AsyncTask
public boolean doComputation() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("activity")
.child(id);
ref.child("path1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Call 1 completed
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
ref.child("path2").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Call 2 completed
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// Some more to do
// Need to return true after execution complete
return true;
}
I can not return true inside of the onDataChange as it will counted for onDataChange method and not for doComputation.
I can perform the calculation by moving it to another method and after each onDataChange callback I can check for variable count, and if it is 2 I can perform the calculations. But after it completes I need to notify it to the calling method that execution is completed.
This is a little bit tricky with Firebase. But, I am really stuck at it right now. Any help will be much appreciated. Thank you.
I have gone with the Tasks API which Firebase uses already. Its great.
As mentioned by #qbix , This answer does the same thing. The example in the answer explains good.
You can also find video link of this API instructions here.
I have tried and tested it. Solves my problem.
Depends on the case, what I usually do is to set a flag after each listener completed its job and then call a method to check the flags. If they are all completed, then do the next operation.
For example
private Boolean listener1Completed;
private Boolean listener2Completed;
public void addListeners() {
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("activity")
.child(id);
ref.child("path1").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
listener1Completed = true;
checkListenerStatus();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
ref.child("path2").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
listener2Completed = true;
checkListenerStatus();
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
}
private void checkListenerStatus() {
if (listener1Completed && listener2Completed) {
// do computation
}
}
Since firebase works in another thread you can't return desired result instantly. You have to create callback to notify the caller when your result already received. You can achieve this using interface read here
Another way. You can get result from Asynctask
Return a value from AsyncTask in Android
Or you can pass your class in parameter (not in AsyncTask job)
public void doComputation(yourCallerClass cls) {
//firebase result.....void
cls.YourResult(trueOrFalse);
.....
}
in your caller class instance eg. yourCallerClass
...
public void YourResult(boolean result){
// do stuff
}
Related
I want to count elements in a firebase database, I have seen different topics and tried this code:
final Query dataQuery = myRef.equalTo(MainActivity.user.getUid()).getRef();
dataQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.e("ERROR",""+dataSnapshot.child(MainActivity.user.getUid()).getChildrenCount());
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "onCancelled", databaseError.toException());
}
});
Now, the value in log error is correct but, if I try to assign it in field, or static field is always 0 out of this method;
How can I use this value in other class?
You need to use a callback and call a method on the callback in your onDataChange. Then once that callback is returned you can continue with the rest of your logic.
You can see an example of that here:
https://github.com/Austin-Android/austin-feeds-me/blob/master/app/src/main/java/com/austindroids/austinfeedsme/data/firebase/FirebaseEventsDataSource.java#L40
fireBase.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Event event = snapshot.getValue(Event.class);
events.add(event);
}
callback.onEventsLoaded(events);
}
#Override
public void onCancelled(DatabaseError firebaseError) {
}
});
You cannot simply take that value and use it outside onDataChange() method, because it will always be null. This is happening because this method has an asynchronous behaviour, which means that is called even before you are getting the data out from the database. A quick fix would be to use that value only inside onDataChange() method, or to dive into the asynchronous world and see the last part of my answer from this post.
When using the following pattern to synchronously get data from Firebase Realtime Database:
String s = Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("Got it");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
}).blockingGet();
It will hang and create an ANR error. If I use the same Firebase "innards" outside of the Single, it fires just fine. The Single without the Firebase code inside also will fire, so it seems there is some incompatibility between the two.
Any ideas?
Firebase delivers events on ui thread, waiting for result with blockingGet deadlocks it. In my opinion you should rethink app logic and subscribe without blocking with subscribe(SingleObserver)
Since you are creating your own Single, You should use DisposableSingleObserver in subscribeWith. Secondly, you shouldn't be calling blockingGet() like that. The reason is by default the Single or any observable/Processor/Flowable you create will be subscribed (run its operations on main thread) and observe on main thread. BlockingGet() causes the mainThread to pause. It's like executing Thread.sleep() on Main Thread. This always ends in a disaster.
The best option for you would be to rethink the logic you are trying to put in to the code. Since the Firebase operations are Async by nature, you should adapt your code to async pattern.
Anyways you can do something like the following to achieve what seems likes you might be trying to do. Note that I wrote the following code here so it might have syntactical errors.
Single.create(new SingleOnSubscribe<String>() {
// your firebase code
#Override
public void subscribe(SingleEmitter<String> e) throws Exception {
FirebaseDatabase.getInstance().getReference("path").orderByChild("child").equalTo("xyz").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
e.onSuccess("My String");
}
#Override
public void onCancelled(DatabaseError databaseError) {
e.onError(databaseError.toException());
}
});
}
})
.subscribeOn(Schedular.io())
.observeOn(AndroidThread.mainThread()) // if you aren't doing intensive/long running tasks on the data you got from firebase
.subscribeWith(new DisposableSingleObserver<String>() {
public void onSuccess(String myString) {
mMyString = myString;
}
public void onError(Throwable t) {
Timber.e("error in fetching data from firebase: %s", t);
}
});
I have been trying to set a value change listener like this:
final DatabaseReference chat_ref =
FirebaseDatabase.getInstance().getReference(Constants.ARG_CHAT_ROOMS +
"/" + room_type_1);
chat_ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
showAlert(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
The problem is that, the parenthesis after the showAlert method keeps looping over, creating several alerts when I only want one.
What could be causing this to loop and how can I address it?
Your help will be most appreciated!
I am not sure where I am getting it wrong, because even this loops as well:
chat_ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
showAlert(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
You have those several alerts because of the way in which ValueEventListener works. But remember ValueEventListener is not a loop is an interface. Please see more details here. And as you see in the offical doc, it is triggered every time the data at a particular location changes.
To solve this, just get the call of the showAlert() out from the onDataChange method.
Hope it helps.
If you add ValueEventListener with addValueEventListener it will fire every time value of you reference changes so showAlert can be displayed several times.
Use addListenerForSingleValueEvent: your listener will be called only once.
https://firebase.google.com/docs/database/android/read-and-write#read_data_once
Consider, I store the following Key-Value pair in Firebase Database.
Key: "CarBrand" and Value: "Audi"
I read the Value for this Key from Firebase Database and display the Value in TextView. For this, I use an EventListener.
The problem I face with an EventListsner is since it runs on a separate thread, the TextView returns a NullPointerException even before the Value is fetched from Firebase.
To overcome the issue I have been using this dirty trick (using a Handler with 500 to 1000 ms delay). Can someone guide me the right way to fetch and display the Value in TextView or any other Views
PS: Apologies for this codeless Question
Thanks
EDIT: More the Code, better the question. The sample code is as follows
String CardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
CarBrand = dataSnapshot.child("CarBrand").getValue(String.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
mTextView.setText(CarBrand);
The above code results in error. To avoid this, I replaced the last line of code with
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mTextView.setText(CarBrand);
}
}, 1000);
So this is what I have been doing. What would be the right way to display the Text in TextView in my Case?
Your approach is wrong as the listener is updating the String CarBrand But since the code is not in sync and casing the Error. You need to update the code
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
String CarBrand =
dataSnapshot.child("CarBrand").getValue(String.class);
updateBrand(CarBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
you need to create a Function to update the value
/* updates the Car brand Text
TODO:: You can do anystuff you want to do after anything gets
updated
*/
public void updateBrand(String brand){
mTextView.setText(CarBrand);
}
NOTE : Put Listener on the Data you are actually looking for changes.
If you want to work with the result of some callback (like ValueEventListener) you should execute your code inside the overriding methods.
String cardBrand;
private ValueEventListener contentListener() {
contentListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Pull Values for all available Keys
carBrand = dataSnapshot.child("CarBrand").getValue(String.class);
mTextView.setText(carBrand);
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
return contentListener;
}
dbContent.addListenerForSingleValueEvent(contentListener());
Let me show you small example:
This class responsible for retreiving the link for you, and it doesn't know what will you do with it, so it takes Callback and pass the result to your callback, where you can do anything you want.
public class SomeDataSource {
void getData(Callback callback) {
String url = Backend.GetUrl();
callback.onDownloadUrlReceived(url);
}
}
this is description of callback. Which you can implement to handle data from SomeDataSource
public interface Callback {
void onDownloadUrlReceived(String url)
}
usage
void onCreate(Bundle savedInstanceState) {
SomeDataSource downloader = new SomeDataSource();
//here callback is implemented, and you can handle data from SomeDataSource like you want.
downloader.getData(new Callback() {
#Override
public void onDownloadUrlReceived(String url) {
do wat you want with url
}
});
}
Currently, I am using firebase Realtime Database. Hence, my data changes come from another thread. Hence, I have no control on when the fresh data update comes over. How do I then know when to call to refresh my UI?
This is my implementation of a swipe to delete in a RecyclerView.
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int pos = viewHolder.getAdapterPosition();
mListRoute.get(pos).removeRoute();
refreshUI();
}
This is the removeRoute() method found in my Route class
public void removeRoute() {
//Delete all the Route Instructions
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId)
.child("Route Instructions");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot routeInstruc : dataSnapshot.getChildren()) {
routeInstruc.getValue(RouteInstructions.class)
.removeRouteInstructions();
}
DatabaseReference mRef2 = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId);
mRef2.removeValue();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
As you can see, the onDataChange() is called by another thread. Which means i do not know when to call my refreshUI() method.
I think i might be able to use a Looper but how do i fit that in the Route class?
Thanks
What you are looking for are callbacks.
Callbacks are practically mandatory when dealing with asynchronous calls, because when you call an asynchronous task, you are basically asking a worker thread to work for you.
It may take 1 second, 10 seconds, 10 minutes, etc, and you can not know for sure. What you can do is delegate that same worker thread and tell her "hey, reply back when you finish the task I gave you".
Enter the callbacks!
You can check for more documentation regarding callbacks here
Say that you have your query defined with the ValueEventListener
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot data) {
}
}
What you should do is have a callback method that replies back as soon as the query listener returns a value (in other words, when your query is executed).
So, have a method like 'onResponseReceivedFromFirebase' and implement it on the callback
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Implement your callback here with the parameters you want (for instance, I used a String there)
public void onResponseReceivedFromFirebase(String argument){
Log.d(MyActivity.class,argument);
}
....
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot data) {
onResponseReceivedFromFirebase("the response arrived!");
}
}
...
}
#Edit
Base on your updated code, I would proceed with the following
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot routeInstruc : dataSnapshot.getChildren()) {
routeInstruc.getValue(RouteInstructions.class)
.removeRouteInstructions();
}
DatabaseReference mRef2 = FirebaseDatabase.getInstance().getReference()
.child("Routes")
.child(routeId);
mRef2.removeValue();
// Implement the callback here
MyActivity.this.onResponseReceivedFromFirebase("We have received a response from dataChanged");
}
#Edit 2 : On Frank van Puffelen's remark, the onDataChange method already runs on the Main Thread, thus allowing you to change any element on the UI.
Very import : If the processing payload of the outcoming data is considerably large, you should pass that same processing into another thread (e.g. An AsyncTask) to avoid making your app non-responsive.
While the Firebase client handles all network and disk IO on a separate thread, it invokes the callback to your code on the main thread. So you can update the UI straight from onDataChange(), without having to worry about the thread yourself.
In fact, all examples in the Firebase documentation update the UI from their onDataChange() (or onChild...()) callbacks. One example from the database quickstart:
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Post object and use the values to update the UI
Post post = dataSnapshot.getValue(Post.class);
mAuthorView.setText(post.author);
mTitleView.setText(post.title);
mBodyView.setText(post.body);
}