android - Find out if childEventListener on Firebase has completed loading all data

android - Find out if childEventListener on Firebase has completed loading all data

Toni Milford Author: Toni Milford Date: 2022-08-21
android - Find out if childEventListener on Firebase has completed loading all data

All you need to know about android - Find out if childEventListener on Firebase has completed loading all data , in addintion to android - Knowing when Firebase has completed API call? , java - About Android Firebase retrieve data(No setter/field error) , Can I find out width of screen programmatically in android app? , android - Firebase Cloud Messaging: how to send data message to all users?

  1. android - Find out if childEventListener on Firebase has completed loading all data
  2. Question:

    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?


    Solution 1:

    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.

    Solution 2:

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

    Solution 3:

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

  3. android - Knowing when Firebase has completed API call?
  4. Question:

    I am having some trouble knowing when my Firebase API call is finished. After reading the Firebase documentation I have found the following:

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

    I understand this to mean that only after all the onChildAdded call is finished, then the ValueEventListener is called. As a result, I thought that I can populate my RecyclerView in the onChildAdded function and then the onSingleValueListener call, I can simply finish animating my loading screen (which has started animating before this function call) and proceed. However, I have run into an issue where I put some careful System.out.println statements and found that in my case, Test 1 is called before Test 2 is ever called. This causes problems because this is actually the opposite behavior of what I wanted: I wanted the onChildAdded function to finish and then call the onSingleValueListener function that prints out Test 1 to be called. Is there any reason why this is happening? Any way around this? I would appreciate an explanation on why this is happening. Thanks!

    public void getComments(final String postId, final Activity activity, final View fragmentView, final View progressOverlay) {
        final Firebase commentsRef = firebaseRef.child("/comments");
        Firebase linkRef = firebaseRef.child("/posts/" + postId);
        linkRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println("Test 1");
                if (progressOverlay.getVisibility() == View.VISIBLE) {
                    progressOverlay.setVisibility(View.GONE);
                    AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
                    fragmentView.findViewById(R.id.rv_view_comments).setVisibility(View.VISIBLE);
                }
            }
    
            @Override
            public void onCancelled(FirebaseError firebaseError) {
    
            }
        });
        linkRef.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                commentsRef.child(dataSnapshot.getKey()).addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        Comment comment = dataSnapshot.getValue(Comment.class);
                        System.out.println("Test 2");
                        application.getCommentsRecyclerViewAdapter().getCommentsList().add(comment);
                        application.getCommentsRecyclerViewAdapter().notifyDataSetChanged();
                    }
    
                    @Override
                    public void onCancelled(FirebaseError firebaseError) {
    
                    }
                });
            }
    
            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
    
            }
    
            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
    
            }
    
            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {
    
            }
    
            @Override
            public void onCancelled(FirebaseError firebaseError) {
    
            }
        });
    } 
    


    Solution 1:

    You may want to use the **FirebaseRecyclerAdapter** class that the Firebase team makes available in FirebaseUI-Android (see https://github.com/firebase/FirebaseUI-Android/blob/master/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java)

    In your gradle file add the line below (check here for latest version number in the readme) compile 'com.firebaseui:firebase-ui-database:0.4.3'

    Solution 2:

    With this code "Firebase linkRef = firebaseRef.child("/posts/" + postId);" I could see that you're using legacy Firebase API. Its deprecated now!

    Kindly update your code to new Firebase 3.x.x API.

    Below two are independent async call; Based on your use-case, you can use either one of the listener to read your data.

    1. linkRef.addListenerForSingleValueEvent(new ValueEventListener() {});
    2. linkRef.addChildEventListener(new ChildEventListener() {});
    

    You can refer the firebase document to get more information about database listeners. https://firebase.google.com/docs/database/android/retrieve-data

    With the following code snippet, you can retrieve and populate your list of comments.

    public void getComments(final String postId, final Activity activity, final View fragmentView, final View progressOverlay) {
        DatabaseReference commentsRef = firebaseRef.child("/comments");
        DatabaseReference linkRef = commentsRef.child("/posts/" + postId);
        linkRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                // Iterate through data-snapshot, and update your Adapter dataset
                for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                    Comment comment = snapshot.getValue(Comment.class);
                    application.getCommentsRecyclerViewAdapter().getCommentsList().add(comment);
                }
                application.getCommentsRecyclerViewAdapter().notifyDataSetChanged();
    
                // Dismiss your loading progressbar
                if (progressOverlay.getVisibility() == View.VISIBLE) {
                    progressOverlay.setVisibility(View.GONE);
                    AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
                    fragmentView.findViewById(R.id.rv_view_comments).setVisibility(View.VISIBLE);
                }
            }
    
            @Override
            public void onCancelled(DatabaseError databaseError) {
                // Handle fail case here
            }
        });
    

    Hope this would help you!

  5. java - About Android Firebase retrieve data(No setter/field error)
  6. Question:

    I just want to retrieve data from my firebase database, but I don't know the correct way to get that data. Here is my error in android studio:

      W/ClassMapper: No setter/field for chapterTwo found on class 
      com.junburg.moon.rockbottom.model.Chapter
      W/ClassMapper: No setter/field for chatperOne found on class 
      com.junburg.moon.rockbottom.model.Chapter
      W/ClassMapper: No setter/field for chapterTwo found on class 
      com.junburg.moon.rockbottom.model.Chapter
      W/ClassMapper: No setter/field for chapterOne found on class 
      com.junburg.moon.rockbottom.model.Chapter
    

    And here is my data classes. This is a Chapter.class model.

    public class Chapter {
    
        private String chaterName;
        private String chaterExplain;
    
        public Chapter() {
        }
    
        public Chapter(String chaterName, String chaterExplain) {
            this.chaterName = chaterName;
            this.chaterExplain = chaterExplain;
        }
    
        public String getChaterName() {
            return chaterName;
        }
    
        public void setChaterName(String chaterName) {
            this.chaterName = chaterName;
        }
    
        public String getChaterExplain() {
            return chaterExplain;
        }
    
        public void setChaterExplain(String chaterExplain) {
            this.chaterExplain = chaterExplain;
        }
    }
    

    And the Study.class model

    public class Subject {
      private String subjectName;
      private String subjectExplain;
      private Chapter chapter;
    
      public Subject() {
      }
    
      public Subject(String subjectName, String subjectExplain, Chapter 
       chapter) {
        this.subjectName = subjectName;
        this.subjectExplain = subjectExplain;
        this.chapter = chapter;
      }
    
       public String getSubjectName() {
          return subjectName;
       }
    
       public void setSubjectName(String subjectName) {
            this.subjectName = subjectName;
       }
    
       public String getSubjectExplain() {
           return subjectExplain;
       }
    
        public void setSubjectExplain(String subjectExplain) {
           this.subjectExplain = subjectExplain;
       }
    
        public Chapter getChapter() {
           return chapter;
       }
    
       public void setChapter(Chapter chapter) {
            this.chapter = chapter;
       }
    }
    

    And this is my addValueEventListener code:

     private void getStudyData() {
          databaseReference.child("study")
         .child("subject")
         .addValueEventListener(new ValueEventListener() {
    
          @Override
          public void onDataChange(DataSnapshot dataSnapshot) {
            subjectList.clear();
            for (DataSnapshot ds : dataSnapshot.getChildren()) {
                Subject subject = ds.getValue(Subject.class);
                subjectList.add(subject);
                Log.d(TAG, "onDataChange: " + 
          subject.getChapter().getChaterName()  + 
          subject.getChapter().getChaterExplain());
    
            }
            studyRecyclerAdapter.notifyDataSetChanged();
    
        }
    
        @Override
        public void onCancelled(DatabaseError databaseError) {
    
        }
    });
    

    Finally, This is my JSON Tree in Firebase database.

    enter image description here

    I tried some methods, but I could not solve this error.

    The code execution does not return the Chapter class data. For example:

    subject.getChapter().getChaterName() 
    subject.getChapter().getChaterExplain()
    

    I would appreciate your help handling this one. And Thank you for caring.


    Solution 1:

    There are several errors in your POJO class of Chapter. First, the error message is clearly saying that the chapterOne and chapterTwo attributes are missing. You need that in your POJO.

    So your Chapter.java should look like this.

    public class Chapter {
        public ChapterOne chapterOne;
        public ChapterTwo chapterTwo;
    }
    

    And the ChapterOne and ChapterTwo classes are needed to be defined as well.

    public class ChapterOne extends ChapterDetails {
    
    }
    
    public class ChapterTwo extends ChapterDetails {
    
    }
    
    public class ChapterDetails {
        public String chapterName;
        public String chapterExplain;
    }
    

    Note that you have misspelled the chapterName and the chapterExplain in your Chapter pojo that you have. The current variable names are chaterName and chaterExplain which is wrong. Please fix the variable names as well.

    Update

    But can i get data to change variable type in chapter data of subject class? List.. Map.. or.. somethnig else like that. Not make new class ChapterOne.. ChapterTwo..

    With your current implementation this is not possible. However, I would suggest a slight change in your firebase database implementation. Let us take the chapter node for the modification.

    chapter
        - chapterDetails
            - chapterId : 1
            - chapterName : Something one
            - chapterExplain : Some explanation
        - chapterDetails
            - chapterId : 2
            - chapterName : Something two
            - chapterExplain : Some explanation
    

    If you modify your firebase database structure like the above, then you might come up wit the following POJO class.

    public class Chapter {
        List<ChapterDetails> chapterDetails;
    }
    
    public class ChapterDetails {
        public Long chapterId;
        public String chapterName;
        public String chapterExplain;
    }
    

    I'm sorry I asked too much. But the same chapterDetail is not added to the Firebases. I tried to add a chapterDetail with chapterId 1 and a chapterDetail with chapterId 2, but it disappears or is replaced.

    I just shown a pseudo implementation. Sorry if the updated answer was misleading. However, please set your data in firebase database using the following. So that you can get your data as a list from firebase.

    Firebase ref = new Firebase("<my-firebase-app>/chapter"):
    List<Chapter> chapterList = new ArrayList<Chapter>();
    // Now populate your chapterList with the chapters 
    addItemsInYourChapterList();
    ref.setValue(chapterList); 
    

    Now while retrieving the data from firebase database like the following.

    ref.addValueEventListener(new ValueEventListener() {
          @Override
          public void onDataChange(DataSnapshot snapshot) {
              System.out.println("There are " + snapshot.getChildrenCount() + " chapters");
              for (DataSnapshot chapterSnapshot: snapshot.getChildren()) {
                Chapter chapter = chapterSnapshot.getValue(Chapter.class);
                // Get your chapter details here.
              }
          }
          @Override
          public void onCancelled(FirebaseError firebaseError) {
              System.out.println("The read failed: " + firebaseError.getMessage());
          }
      });
    

    Please note that I am taking the chapter node into consideration only. Modify the code as per your need.

    Solution 2:

    With this code "Firebase linkRef = firebaseRef.child("/posts/" + postId);" I could see that you're using legacy Firebase API. Its deprecated now!

    Kindly update your code to new Firebase 3.x.x API.

    Below two are independent async call; Based on your use-case, you can use either one of the listener to read your data.

    1. linkRef.addListenerForSingleValueEvent(new ValueEventListener() {});
    2. linkRef.addChildEventListener(new ChildEventListener() {});
    

    You can refer the firebase document to get more information about database listeners. https://firebase.google.com/docs/database/android/retrieve-data

    With the following code snippet, you can retrieve and populate your list of comments.

    public void getComments(final String postId, final Activity activity, final View fragmentView, final View progressOverlay) {
        DatabaseReference commentsRef = firebaseRef.child("/comments");
        DatabaseReference linkRef = commentsRef.child("/posts/" + postId);
        linkRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                // Iterate through data-snapshot, and update your Adapter dataset
                for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                    Comment comment = snapshot.getValue(Comment.class);
                    application.getCommentsRecyclerViewAdapter().getCommentsList().add(comment);
                }
                application.getCommentsRecyclerViewAdapter().notifyDataSetChanged();
    
                // Dismiss your loading progressbar
                if (progressOverlay.getVisibility() == View.VISIBLE) {
                    progressOverlay.setVisibility(View.GONE);
                    AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
                    fragmentView.findViewById(R.id.rv_view_comments).setVisibility(View.VISIBLE);
                }
            }
    
            @Override
            public void onCancelled(DatabaseError databaseError) {
                // Handle fail case here
            }
        });
    

    Hope this would help you!

  7. Can I find out width of screen programmatically in android app?
  8. Question:

    Can I find out the width of screen programmatically in android app? I need to paint canvas but its width should be almost like screen and I could not set match_parent in java part program.


    Solution 1:

    You can get default Display instance and then read width/height from it:

    int width = getWindowManager().getDefaultDisplay().getWidth();
    int height = getWindowManager().getDefaultDisplay().getHeight();
    

    Solution 2:

    You can do this:

    DisplayMetrics dm = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(dm);
    

    dm.widthPixels contains the width and dm.heightPixels contains the height.

    Solution 3:

    You could also overwrite onSizeChanged(int,int,int,int) in any view and set it in there.

  9. android - Firebase Cloud Messaging: how to send data message to all users?
  10. Question:

    I would like to send data messages to all users.

    Is it possible to do it programmatically, without using the Firebase Notifications Console?

    The problem with the Console is that the "message text" field at the beginning of the form is compulsory. So, even if I add the custom data key/values, there will also be the standard notification component.

    As stated here, when the message includes both notification and data, in case the app is in the background, a standard notification message will be notified to the system tray.

    I would like instead to deliver only a notification based on the custom data (trigged by OnMessageReceived).

    How can I achieve that? Programmatically, I can correctly send data messages to specific users, but I cannot find a way to send data messages to ALL users.


    Solution 1:

    Use topic messaging. You can define the name of a topic that all installations of your app will subscribe to, then send the message to that topic.

    You can use the Firebase Admin SDK from your server to send that message. Or you can use the FCM HTTP API to send that message.