Captain Jacky
Captain Jacky

Reputation: 1059

Realtime Database how to wait for asynchronous task in iteration?

What I'm doing here is getting users groups as hashmap, then getting each group's info and doing other stuff with it. But at the end of the foreach loop my "groups" list is empty. Is there anything I can do to wait for foreach loop to finish? Thank you.

db
    .child("users")
    .child(firebaseUser.uid)
    .get()
    .addOnCompleteListener { task ->
        if (task.isSuccessful) {
        val document = task.result

        if (document != null && document.value != null) {
            val groupsObject =
                document.value["groups"] as HashMap<String, Boolean>

            for (groupID in groupsObject.keys) {
                db
                    .child("groups/$groupID")
                    .addValueEventListener(object : ValueEventListener {
                        override fun onDataChange(snapshot: DataSnapshot) {
                            db
                                .child("users")
                                .child(friendID)
                                .get()
                                .addOnCompleteListener { task ->
                                    // check for something
                                    // add to groups list if data is OK
                                }
                        }

                        override fun onCancelled(error: DatabaseError) {
                            // TODO: ...
                        }
                    })
            }

            // here "groups" data is empty, because that for each loop above
            // hasn't finshed yet looping, and thus my list is displayed empty
            groupsAdapter = GroupsAdapter(this@GroupsActivity, groups)
            recyclerView.adapter = groupsAdapter
        }
    }
}

Upvotes: 1

Views: 288

Answers (1)

Frank van Puffelen
Frank van Puffelen

Reputation: 600131

There is no way to wait for all data to be loaded without blocking the user from using your app. That's why the rule of thumb is always the same: any code that needs the data from the database needs to be inside the addOnCompleteListener callback, or be called from there.


The idiomatic way is to not wait for the data to be loaded, but instead tell the adapter about the new data as it comes in. You can do that by calling notifyDataSetChanged inside your addOnCompleteListener:

db
    .child("groups/$groupID")
    .addValueEventListener(object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            db
                .child("users")
                .child(friendID)
                .get()
                .addOnCompleteListener { task ->
                    // check for something
                    // add to groups list if data is OK
                    groupsAdapter.notifyDataSetChanged()
                }
        }

In cases where you really must run some code after all data has loaded, a common approach is to keep a counter of the number of friends you've already loaded, and check against that inside the addOnCompleteListener.

So something like:

.addOnCompleteListener { task ->
    // check for something
    // add to groups list if data is OK
    
    if (friendsLoadedCount++ === groupsObject.keys.size()) {
        // TODO: all friends have loaded, so do something with them
    }
}

Upvotes: 1

Related Questions