Call method in another thread on completion - android

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);
}

Related

How to use a value outside dataSnapshot function?

In the code below i get the number of children but i want to use it outside the onDataChange method.
mRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
DateStorage dateStorage = null;
for (DataSnapshot result : dataSnapshot.getChildren())
{
Log.e(result.getKey(),result.getChildrenCount() + "");
in[0] = result.getChildrenCount();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Can anyone help me?
Data is loaded from Firebase asynchronously. Your main code continues to run while the data is loading, and then when the data is available the onDataChange method is called. What that means is easiest to see if you add a few log statements:
Log.d("TAG", "Before attaching listener");
mRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
Log.d("TAG", "Got data");
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
Log.d("TAG", "After attaching listener");
When you run this code, it logs:
Before attaching listener
After attaching listener
Got data
This is probably not the order you expected, but is completely normal when calling asynchronous APIs. And it explains why you'll get the wrong value if you print it outside of the onDataChange().
The problem is not that you can't use the data outside of the onDataChange(), the problem is that you must ensure that onDataChange() has run before you use the data.
The simplest way to do that is to put all code that requires data from the database inside the onDataChange method. But you can also create your own callback interface, and pass that into the method where you load the data. For an example of both of these approaches, see my answer here: getContactsFromFirebase() method return an empty list

Single.Create()...blockingGet() hangs in RxJava 2.1.3 when using Android/Firebase Realtime Database

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);
}
});

Read Firebase Data Once With ValueEventListener

Within my app I often have the need to read data once. I originally started by using the addListenerForSingleValueEvent() method for this, however I ran into problems using this method as it does not work as I wanted when offline capabilities are enabled (see here the issue: Firebase Offline Capabilities and addListenerForSingleValueEvent)
In the question above it is mentioned that a workaround is to use the addValueEventListener() method, however I do not fully understand how to do this (particularly how to remove the ValueEventListener as soon I am finished grabbing the data I need).
Take this method which I created in a standalone class to query the Users node on Firebase where I store the users FCM Token. It seems to have an issue of not returning the latest token from the server everytime.
public class SendFCMMessage {
String userToken;
String currentUser;
String userName;
ValueEventListener userListener;
public void sendMessage(final String contactNumber) {
final DatabaseReference ref = FirebaseDatabase.getInstance().getReferenceFromUrl(link).child("Users").child(contactNumber);
userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
userToken = user.getToken();
// Send FCM Message after getting user token and then remove event listener
ref.removeEventListener(userListener);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("TAG", "Something terrible went wrong: " + databaseError);
}
};
ref.addValueEventListener(userListener);
}
}
If I remove the line
ref.removeEventListener(userListener);
Then this code works fine, however I would like to know how I could remove the ValueEventListener as soon as I receive the data I need?
Thanks,
R
ValueEventListener vel; //Declared Global
Listen your DatabaseReference like this;
vel = yourDatabaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChanged(DataSnapshot dataSnapShot) {
//Do your stuff here. I suggest you create another method for this if you don't want a problem with inner class.
//For example, workDataSnapshot(DataSnapshot dataSnapShot) <-- Work here
yourDatabaseReference.removeEventListener(vel);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope it helps you.

Firebase Database Datasnapshot

I have a firebase database from which I save and retrieve data from, to and from. I know how datasnapshot works inside an addValueEventListener. The problem is that this is only called or triggered when the firebase database detects change in its data. I only want to access data and read it to be able to store it in an arraylist or the same thing.
I have a code like this:
public void foo(){
DatabaseReference x= FirebaseDatabase.getInstance().getReference().child("x");
reservations.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String userID = client.getId();
for(DataSnapshot snap : dataSnapshot.getChildren()){
if(snap.child("someId").equals(someId)) number++;
if(snap.child("userID").getValue().equals(client.getId())){
isAlreadyReserved = true; // if user has already reserved the item
alreadyReserved();
break;
}
Log.e("isAlreadyReserved: ", isAlreadyReserved+"");
numberOfReservations++;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isAlreadyReserved) {
alreadyReserved();
}
else if(number == numberOfCopies){
// material is no longer available
OtherActivity.showMaterialUnavailable();
}
else{
Reservation reservation = new Reservation();
reservation.setBookId(this.bookId);
reservation.setResID((numberOfReservations+1)+"");
reservation.setUserID(client.getId());
String key = reservations.push().getKey();
reservations.child(key).setValue(reservation);
Log.e("Reservations: ", "reserve successful");
AlertDialog.Builder builder = new AlertDialog.Builder(this.context);
builder.setTitle(R.string.reservationSuccess_title)
.setMessage(R.string.reservationSuccess_body);
AlertDialog dialog = builder.create();
dialog.show();
}
}
You can see that inside onDataChange I only count materials and set some flags, which I can supposedly do outside the ValueEventListener.
But I notice that this is faulty because onDataChange is called only when writing to the Firebase database occurs. Which should not be the case.
What can I do to loop through the values inside the DatabaseReference x without calling onDataChange, or without using DataSnapshot?
You cannot loop inside a DatabaseReference without using a listener. When we are talking about Firebase, we are talking only about listeners. So in order to get those values, you need to use a listener and than get the data out from the dataSnapshot.
What i think your problem is in your case, is that onDataChange method is called in an asynchronously way. This means that everything you are doing outsite this method is actually executed before onDataChange method has been called. So in order to understand what is actually going on, please see this post and this post. Reading this posts, will teach you how to query data in Firebase and how to retrieve data from Firebase asynchronously.
Hope it helps.
In order to get the values of DatabaseReference x, you should use addListenerForSingleValueEvent
x.addListenerForSingleValueEvent(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
//do something
}
#Override
public void onCancelled(DatabaseError databaseError)
{
//do something
}
});
as mentioned in the firebase documentation:
public void addListenerForSingleValueEvent (ValueEventListener
listener)
Add a listener for a single change in the
data at this location. This listener will be triggered once with the
value of the data at the location.

Continue execution after data received from multiple location in Firebase

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
}

Categories

Resources