47

I'm using Firebase Realtime Database for storing and retrieving data for my Android application. In my activity I retrieve all data (example: list of user data) from Firebase using a childEventListener.

I want to show a progress bar as long as the data is not completely retrieved from the database. How do I check if all data is completely retrieved so that I can close the progress bar after the data is loaded?

Grimthorr
  • 6,856
  • 5
  • 41
  • 53
Stanley Giovany
  • 583
  • 1
  • 7
  • 12

3 Answers3

96

There is a common way to detect when Firebase is done synchronizing the initial data on a given location. This approach makes use of one of the Firebase event guarantees:

Value events are always triggered last and are guaranteed to contain updates from any other events which occurred before that snapshot was taken.

So if you have both a ValueEventListener and a ChildEventListener on a given location, the ValueEventListener.onDataChange() is guaranteed to be called after all the onChildAdded() calls have happened. You can use this to know when the initial data loading is done:

ref.addListenerForSingleValueEvent(new ValueEventListener() {
    public void onDataChange(DataSnapshot dataSnapshot) {
        System.out.println("We're done loading the initial "+dataSnapshot.getChildrenCount()+" items");
    }
    public void onCancelled(FirebaseError firebaseError) { }
});
ref.addChildEventListener(new ChildEventListener() {
    public void onChildAdded(DataSnapshot dataSnapshot, String previousKey) {
        System.out.println("Add "+dataSnapshot.getKey()+" to UI after "+previousKey);
    }
    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
    }
    public void onChildRemoved(DataSnapshot dataSnapshot) {
    }
    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
    }
    public void onCancelled(FirebaseError firebaseError) { }
});

In my test run this results in:

Add -K2WLjgH0es40OGWp6Ln to UI after null
Add -K2YyDkM4lUotI12OnOs to UI after -K2WLjgH0es40OGWp6Ln
Add -K2YyG4ScQMuRDoFogA9 to UI after -K2YyDkM4lUotI12OnOs
...
Add -K4BPqs_cdj5SwARoluP to UI after -K4A0zkyITWOrxI9-2On
Add -K4BaozoJDUsDP_X2sUu to UI after -K4BPqs_cdj5SwARoluP
Add -K4uCQDYR0k05Xqyj6jI to UI after -K4BaozoJDUsDP_X2sUu
We're done loading the initial 121 items

So you could use the onDataChanged() event to hide the progress bar.

But one thing to keep in mind: Firebase doesn't just load data. It continuously synchronizes data from the server to all connected clients. As such, there is not really any moment where the data is completely retrieved.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Great answer! thanks for your answer. It works! your answer really helpful. but i want to ask something, why addListenerForSingleValueEvent onDataChange still invoked when there's change in the reference? is addListenerForSingleValueEvent just invoked once then it removed or i'm false? – Stanley Giovany Dec 30 '15 at 16:25
  • When you add a listener with `addListenerForSingleValueEvent()` it is invoked for exactly one value event. After that it is removed automatically. Read more about these in the documentation: https://www.firebase.com/docs/android/guide/retrieving-data.html#section-reading-once – Frank van Puffelen Dec 30 '15 at 16:42
  • so, `onDataChange()` will be invoked everytime the reference in firebase is changed or added? or just invoked once then the listener is removed? because when i'm using `addListenerForSingleValueEvent()` , after `onDataChange()` is invoked, the listener is not removed from the reference, and when i'm add new value in the reference, the `onDataChange()` is invoked again. – Stanley Giovany Dec 30 '15 at 16:49
  • 1
    That sounds odd. I just added a new item and only got: `Add -K6ns123jl6xQXfQJR_r to UI after -K4uCQDYR0k05Xqyj6jI` If you still experience that behavior (and are certain you only add the listener with `addListenerForSingleValueEvent()`, open a new question and include the minimal code required to reproduce your problem. See http://stackoverflow.com/help/mcve – Frank van Puffelen Dec 30 '15 at 17:13
  • 3
    It would really be nice if you could do `dataSnapshot.parent().getChildrenCount()` in `onDataChange()`. I assume using `addListenerForSingleValueEvent()` to get the number of items, will load *all* the data (that was just loaded in `onDataChange()`) *again* ? – lenooh Jul 20 '16 at 19:20
  • 5
    Rem : this solution doesn't seems to work if some items are already in the client cache (android device). In that case the onDataChange() event retrieve the element from the cache before the ChildEventListener finish to synchronize all the child from the server !! – ThierryC Jan 26 '17 at 10:03
  • @toofoo If that is the case, is there an alternative? – Sreekanth Karumanaghat Aug 28 '17 at 07:27
  • shouldnot we remove addChildEventListener at some point of time, is it fine that we have it there all the time? – Sreekanth Karumanaghat Aug 28 '17 at 07:28
  • 2
    This works only for the first time. When data is cached onDataChange() is called first – Klemenko Sep 06 '17 at 11:51
  • That was what OP needed. If you want to get called whenever each set of data/change has completely loaded, use `addSingleValueEventListener()`. – Frank van Puffelen Sep 06 '17 at 14:22
  • 3
    But wouldnt `ChildEventListener` fetch all children and not just the children that are added? So then youd have 2 duplicate sets, one from the `SingleValueEventListener` and another with `ChildEventListener`. – James Heald Oct 05 '17 at 14:52
  • Major flaw with this. If you call dataSnapshot.getChildrenCount() within your SingleValueEvent, you will notice that you are retrieving duplicate data. If you have a lot of users and lot of items being returned, this can cause major issues regarding server load and cost. – ChallengeAccepted Apr 01 '18 at 22:37
  • 1
    @ChallengeAccepted If you have two listeners on the same reference at once time (as in the code in my answer), the data is only synchronized once. If you want to verify this, [enable debug logging](https://firebase.google.com/docs/reference/android/com/google/firebase/database/FirebaseDatabase.html#setLogLevel(com.google.firebase.database.Logger.Level)) and check your logcat output to see the wire traffic. – Frank van Puffelen Apr 02 '18 at 01:25
  • @FrankvanPuffelen I did indeed log the output. I logged the dataSnapshot for both the childEventListener and the SingleEventListener. The childEventListener was limited to a count of 10, however the singleEventListener dataSnapshot returned ALL of the items within that reference, rather than just returning 10. Could you please tell me what you used to monitor the web traffic? Did you use the Firebase Realtime Database profiler tool? – ChallengeAccepted Apr 05 '18 at 01:40
  • If you attach the listeners to the same reference or query, they will get the same data. If that doesn't happen for you, open a new question with the [minimal, standalone, complete code that reproduces the problem you have](http://stackoverflow.com/help/mcve). – Frank van Puffelen Apr 05 '18 at 02:34
  • Good, but this loads all data twice and creates unnecessary server costs – mathematics-and-caffeine Apr 07 '22 at 18:41
  • No it doesn't: If you have two listeners on the same reference at once time (as in the code in my answer), the data is only synchronized once. See my comment 4 lines up: https://stackoverflow.com/questions/34530566/find-out-if-childeventlistener-on-firebase-has-completed-loading-all-data/34532739?noredirect=1#comment86215556_34532739 – Frank van Puffelen Apr 07 '22 at 18:49
13

I have done it in quite simple way. I let an int count and then every time when it comes inside the function , i increment it and check it whether it is equals to the total no of childs . if it's equal then there you can stop the progress bar

    int count = 0;    
    ref.addChildEventListener(new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            count++;


            if(count >= dataSnapshot.getChildrenCount()){
                //stop progress bar here
            }
        }
Chetan
  • 4,735
  • 8
  • 48
  • 57
osama
  • 1,286
  • 18
  • 22
  • and my engish is little weak :D – osama Aug 14 '16 at 17:37
  • How do you get rid of listener after? Or do we not have to worry about that? – Lion789 Nov 03 '16 at 03:21
  • and sorry for the late reply ... :D – osama Nov 08 '16 at 14:26
  • 3
    you have to always remove the listeners, when you don't need them. The only listener you shouldn't remove it the addListenerForSingleValueEvent(), which is automatically removed when the event is fired – user2924714 Dec 22 '16 at 06:41
  • 7
    But getChildrenCount() returns this query's children count or node's children count? Because if it was the second, this method would give a wrong answer in case there's a filter. – Hugo Passos Oct 24 '17 at 19:32
0

Use

  totalChilds = 0; 
  ref..addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                    totalChilds = dataSnapshot.getChildrenCount();
                    ref.addChildEventListener(new ChildEventListener() {
                           @Override
                           public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                           count++;
                           if(count >= totalChilds){
                                 progressDialog.dismiss();
            }
            @Override
        public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

                }

                @Override
                public void onCancelled(@NonNull DatabaseError databaseError) {

                }
            });