skip to Main Content

I am a beginner in Android development. I am trying to implement RecyclerView which shows a list of groups after downloading it from the Realtime Database.
The function loadGroups() is called from the main activity to return a list which is then fed to the RecyclerView adapter.
The data is being downloaded correctly but seems like myList is returned first and elements from firebase are added a few millis later. I want the program to wait for the elements to be added to myList and then return it

class DataSource {
private lateinit var myDatabase: DatabaseReference
var myList : MutableList<Group> = mutableListOf<Group>();

fun loadGroups(): MutableList<Group> {
    // Here I want to let the loadGroupsFromFirebase() complete adding groups to mylist 
    // after that is completed, mylist should be returned
    loadGroupsFromFirebase()
    
    Log.d("mylist", "returning my list")
    return myList
}

private fun loadGroupsFromFirebase(){
    myDatabase = FirebaseDatabase.getInstance().getReference("myGroupsList")

    val postListener = object : ValueEventListener {
        override fun onDataChange(myDataSnapshot: DataSnapshot) {
            if(myDataSnapshot.exists()){
                Log.d("mylist", "does exist ${myDataSnapshot.getValue().toString()}")
                myList.clear()

                for(dataSnapshot in myDataSnapshot.children){
                    val myGroupDetails = dataSnapshot.getValue<Group>()!!;
                    myList.add(myGroupDetails)
                    myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
                    Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
                }
            }
            else {
                Log.d("mylist", "does not exist")
            }
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Post failed, log a message
            Log.w("mylist", "loadPost:onCancelled", databaseError.toException())
        }
    }
    myDatabase.addValueEventListener(postListener)
}

}

Any help would be appreciated 🙂
Below is the screenshot of logcat.

log screenshot

3

Answers


  1. ValueEventListener is an interface, so that everything that runs on onDataChange is an asyncronous operation (because depends on network response), in other hand loadGroups() is a synchronous operation, so loadGroupsFromFirebase() is call at first and inmediatly return myList is called, but loadGroupsFromFirebase() is not finish yet.

    So you need to use the groups after the for loop

        for(dataSnapshot in myDataSnapshot.children){
                            val myGroupDetails = dataSnapshot.getValue<Group>()!!;
                            myList.add(myGroupDetails)
                            myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
                            Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
                        }
    
    //-- HERE, TAKE myList and save it somehow.
    

    Or well, implements MVP or MVVM patters, so you can handle the asyncronous response properly.

    Login or Signup to reply.
  2. Modify your function loadGroupsFromFirebase() have a unit return

    private fun loadGroupsFromFirebase(result: (List<Group>) -> Unit){
    myDatabase = FirebaseDatabase.getInstance().getReference("myGroupsList")
    
    val postListener = object : ValueEventListener {
        override fun onDataChange(myDataSnapshot: DataSnapshot) {
            if(myDataSnapshot.exists()){
                Log.d("mylist", "does exist ${myDataSnapshot.getValue().toString()}")
                myList.clear()
    
                for(dataSnapshot in myDataSnapshot.children){
                    val myGroupDetails = dataSnapshot.getValue<Group>()!!;
                    myList.add(myGroupDetails)
                    myList.add(Group(myIconId=2131165282, myTitle="G1", myLink = "https://s*****************************************9", numberOfPeople=100))
                    Log.d("mylist", "does exist CODE 00 ${myList.toString()}")
                }
            }
            else {
                Log.d("mylist", "does not exist")
            }
            result(myList)
        }
    
        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Post failed, log a message
            Log.w("mylist", "loadPost:onCancelled", databaseError.toException())
        }
    }
    myDatabase.addValueEventListener(postListener)
    

    }

    Login or Signup to reply.
  3. There is no way you can synchronically return myList as a result of a method because Firebase API is asynchronous. The solution is always the same, any code that needs data from the Realtime Database needs to be inside the onDataChange() method, or be called from there.

    Since you’re using Kotlin, please note that we have a simpler way to solve the asynchronous calls. So I’ll show you how to use Kotlin Coroutines.

    In order to make it work, we need to add the following dependency in our build.gradle file:

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4"
    

    This library that we use is called Module kotlinx-coroutines-play-services. As I mentioned before, there is no way we can return a list of objects as a result of a method because get() returns immediately, while the callback from the Task it returns will be called sometime later. That’s the reason why we should wait until the data is available.

    When calling "get()" on the Task object that is returned, we can attach a listener so we can get the result of our query. What we need to do now is to convert this into something that is working with Kotlin Coroutines. For that, we need to create a suspend function that looks like this:

    private suspend fun getGroupList(): List<DocumentSnapshot> {
        val snapshot = myDatabase.get().await()
        return snapshot.documents
    }
    

    As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the Realtime Database is available and then return it. Now we can simply call it from another suspend method like in the following lines of code:

    private suspend fun getGroupListFromFirestore() {
        try {
            val groupList = getGroupList()
        } catch (e: Exception) {
            Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
        }
    }
    

    And that’s pretty much of it. If you are interested, I have also written an article called:

    That will help you better understand the concept.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search