The problem: the layout in my MainActivity is generated before I have a chance to finish calling the firebase to restore app data. If I rotate the screen, thus causing onCreate to run again in the MainActivity, everything is generated just fine.
In my app, I have a custom implementation of the Application class, which makes a bunch of calls to Firebase in order to restore data / make sure data is always in sync. However, instead of having just a few ValueEventListeners with a ton of children underneath, I have around 20 ValueEventListeners. This is to prevent my app from syncing nearly the entire database every time the user generates a tiny bit of data, and to avoid conflicts that can happen when data is manipulated asynchronously. Interestingly, the ValueEventListeners don't actually fetch their data in the order that they're coded in, so I can't set a bool to true when the last one is done.
I'm wondering if there's a simple way to detect whether the firebase reads are all done, other than placing some code at the end of every single Listener and performing an operation that does this manually. I've looked at the methods available in the Application class, as well as Firebase, and so far I haven't found anything that works.
some code from my Application class:
public class CompassApp extends Application {
... then inside the Application's onCreate:
// Fetching Data from DB.
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference dbRef = database.getReference();
// Current User Data
dbRef.child("currentAppData").child("workingOut").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
activeFirebaseConnections += 1;
// Stops executing method if there is no data to retrieve
if (!dataSnapshot.exists()) {
return;
}
workingOut = dataSnapshot.getValue(boolean.class);
activeFirebaseConnections -= 1;
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "Firebase read of sleepDebt failed");
}
});
dbRef.child("currentAppData").child("sleeping").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
activeFirebaseConnections += 1;
// Stops executing method if there is no data to retrieve
if (!dataSnapshot.exists()) {
return;
}
sleeping = dataSnapshot.getValue(boolean.class);
activeFirebaseConnections -= 1;
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "Firebase read of sleepDebt failed");
}
});
(and so on... the rest is just more ValueEventListeners)
I'm currently using a utility class that I created for kicking off multiple loads of data. It will trigger a final Task when all of the listeners are complete. They key here is clever use of the new Task facility for asynchronous programming provided by Play Services. You could probably do all this using some other async framework, but Tasks come for free with Play Services and are used in other parts of Firebase, so you may as well learn to use them. :-)
I gave a talk at Google I/O 2016 about tasks. Here is a link to a video that jumps directly to the relevant part of that session.
The way you use it is like this. Determine all the
DatabaseReference
s you need to load data from and store them in members of your Activity. Then, during your activityonStart()
, pass them all into an instance of FirebaseMultiQuery and callstart()
on it. It will return a Task that generates a Map of DatabaseReference to the DataSnapshot it will generate. With that Task in hand, register a final listener that will trigger when all the data is loaded:And in your
onStop()
, don't forget to callstop()
on it to make sure everything is shut down properly:The listener triggered by the completion of the task that receives all the data in a map can look something like this:
It's perhaps worth noting that Firebase Database calls that write data also return Tasks that you can listen on for completion. Also, Firebase Storage uses Tasks, and so does Firebase Authentication.
The code here may not currently work with a Firebase Database Query that is not a simple named reference into a node in your database. It depends on whether they are hashed in a way that's consistent for keying into a HashMap.
The code above is still somewhat experimental, but I haven't had any problems with it so far. It seems like a lot of code, but if you do a lot of concurrent loading, it's really handy.
I was facing the same problem with my app. After a long research on how to check if firebase is done retrieving data, I found this solution.
So when you have multiple event listeners in your app. Firebase doesn't execute this listeners one by one in sequence.
The Value Event Listener is always executed at last.
So if you want to execute something when all the data is retrieved, you can call a ValueEventListener and execute a method which loads all your views inside it.
So use
rootRef.addListenerForSingleValueEvent()
and inside itsonDataChanged()
execute what you want to execute after data is retrieved from Firebase.This worked for me, Hope it works for you too