Sunday 8 January 2017

conquer the asynchronous behavior of callbacks.

Hey, I'm writing after a long time. Okay so why am I writing here? (even I don't know).

Anyways, one of the pains in my ass has been controlling the asynchronous behavior of callbacks and synchronizing it with my code control statements.

Example : Using addValueEventListener in firebase. What happens actually Firebase loads and synchronizes data asynchronously. Now you know what all difficulties may be faced due to this. Consider one example:



private boolean checkQuizStartTime()
{
    FirebaseDatabase database = FirebaseDatabase.getInstance();

    final int[] flag = {0};

    DatabaseReference mainDbRef = database.getReferenceFromUrl("https://....com/");

    DatabaseReference quizStartTime = mainDbRef.child("quiz_start");

    quizStartTime.addValueEventListener(new ValueEventListener() {
        @Override        public void onDataChange(DataSnapshot dataSnapshot) {

            String value = dataSnapshot.getValue(String.class);
            if (value.equals("yes"))
                flag[0] = 1;
            else                flag[0] = 0;
        }

        @Override        public void onCancelled(DatabaseError databaseError) {

        }
    });

    return flag[0] == 1;
}

The Method I wrote above always returns false no matter what value is present at key "start_quiz". Again the reason is the asynchronous execution of the callback. By the time the value of flag[0] gets updated the methods returns.

Anyways there as much trivial (jugadu in Hindi) solutions to it. Previously when I use to encounter such situation I use this trivial method only. The example in this situation you can use SharedPreference when you store the value inside the onDataChanged method and later you can access the value stored inside the SharedPreference file.


Another way can be waiting for few seconds until the asynchronous call gets completed and updated the value written inside the onDataChanged method. You can use Handler like this:



new android.os.Handler().postDelayed(
        new Runnable() {
    public void run() {
    }
}, 2000);


But doing all these things may work for some particular test instances. But this is not going to make our application scalable. So let us try to understand the reason behind this and learn proper concepts for it.



You have two choices for dealing with the asynchronous nature of this loading:
  1. squash the asynchronous bug (usually accompanied by muttering of phrases like: "it was a mistake, these people don't know what they're doing")
  2. embrace the asynchronous beast (usually accompanied by quite some hours of cursing, but after a while by peace and better behaved applications)

make the asynchronous call behave synchronously


For this we can use Semaphore class, we can create its object before the callback and use release method on its object inside the onDataChagned method.

Semaphore semaphore = new Semaphore(0);
....callback
onDataChanged()
....semaphore.release();


But there are problems I faced on my android. It blocked my app however it worked fine on JVM.

A more Ideal way is to 

embrace the asynchronous nature of data synchronization in Firebase


If you instead choose to embrace asynchronous programming, you should rethink your application's logic.
You currently have "First load the bookmarks. Then load the sample data. And then load even more."
With an asynchronous loading model, you should think like "Whenever the bookmarks have loaded, I want to load the sample data. Whenever the sample data has loaded, I want to load even more." The bonus of thinking this way is that it also works when the data can be changed and thus synchronized multiple times: "Whenever the bookmarks change, I want to also load the sample data. Whenever the sample data changes, I want to load even more."
In code, this leads to nested calls or event chains:
private boolean checkQuizStartTime()
{
    FirebaseDatabase database = FirebaseDatabase.getInstance();

    final int[] flag = {0};

    DatabaseReference mainDbRef = database.getReferenceFromUrl("https://-.,,.com/");

    DatabaseReference quizStartTime = mainDbRef.child("quiz_start");

    quizStartTime.addValueEventListener(new ValueEventListener() {
        @Override        public void onDataChange(DataSnapshot dataSnapshot) {

            String value = dataSnapshot.getValue(String.class);
            myMethod(value);
        }

        @Override        public void onCancelled(DatabaseError databaseError) {

        }
    });

    return flag[0] == 1;
}
In the above code, we don't just wait for a single value event, we instead deal with all of them. This means that whenever the bookmarks are changed, the isonDataChange executed and we (re)load the sample data (or whatever other action fits your application's needs).

No comments:

Post a Comment