mr. groot
mr. groot

Reputation: 374

How to use kotlin coroutines with for a firestore query

I have created an app with Kotlin and Firebase Firestore. Now I need to implement coroutines as there is so much work on the main thread. But I'm also a beginner so it's something new to me. I've watched some tutorials on this but I didn't find complete tutorials on Firestore with coroutines. So I need some help to implement coroutines in my app In such parts like these (I tried by myself but didn't get it).

Retrieving posts from Firestore.

private fun retrievePosts() {
             FirebaseFirestore.getInstance().collection("Posts")
            .orderBy("timeStamp", Query.Direction.DESCENDING)
            .get()
            .addOnSuccessListener { queryDocumentSnapshots ->
                postList?.clear()
                for (documentSnapshot in queryDocumentSnapshots) {
                    val post = documentSnapshot.toObject(Post::class.java)
                    postList?.add(post)
                }
                postAdapter?.notifyDataSetChanged()
                postAdapter?.setOnPostClickListener(this)
                if (isRefreshed) {
                    swipe_refresh_home?.setRefreshing(false)
                    isRefreshed = false
                }
                swipe_refresh_home?.visibility = VISIBLE
                progress_bar_home?.visibility = GONE
            }.addOnFailureListener { e ->
                Log.d(TAG, "UserAdapter-retrieveUsers: ", e)
                swipe_refresh_home?.visibility = VISIBLE
                progress_bar_home?.visibility = GONE
            }
    }

Getting user data into an adapter

private fun userInfo( fullName: TextView, profileImage: CircleImageView,
                      about: TextView, uid: String,
                      userLocation: TextView, itemRoot: LinearLayout ) {

        val userRef = FirebaseFirestore.getInstance().collection("Users").document(uid)
        userRef.get()
                .addOnSuccessListener {
                    if (it != null && it.exists()) {
                        val user = it.toObject(User::class.java)
                        Glide.with(mContext).load(user?.getImage()).placeholder(R.drawable.default_pro_pic).into(profileImage)

                        fullName.text = user?.getFullName().toString()

                        about.text = user?.getAbout()

                        if (user?.getLocation() != ""){
                            userLocation.visibility = VISIBLE
                            userLocation.text = user?.getLocation()
                        }

                        if (profileImage.drawable == null){
                            itemRoot.visibility = GONE
                        }
                        else{
                            itemRoot.visibility = VISIBLE
                        }
                    }
                }
    }

And this Save post button in an adapter.

private fun savedPost(postId: String, saveButton: ImageView?) {
        FirebaseFirestore.getInstance().collection("Users").document(currentUserID)
                .collection("Saved Posts").document(postId)
                .get()
                .addOnSuccessListener {
                    if (it.exists()) {
                        saveButton?.setImageResource(drawable.ic_bookmark)
                    } else {
                        saveButton?.setImageResource(drawable.bookmark_post_ic)
                    }
                }
    }

Upvotes: 5

Views: 4340

Answers (2)

Alex Mamo
Alex Mamo

Reputation: 138834

As I see your code, you are using the following query:

val queryPostsByTimestamp = FirebaseFirestore.getInstance().collection("Posts")
        .orderBy("timeStamp", Query.Direction.DESCENDING)

Most probably to get a list of Post objects from your "Posts" collection.

In order to use Kotlin Coroutines, don't forget to add the following dependencies in the Gradle (app) file:

 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
 implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

I'll provide you a solution using the MVVM architecture pattern. So we'll use a repository class and a ViewModel class. For the asynchronous calls to Firestore, we'll use Flow.

For the response that we get from the database call, we need a sealed class that looks like this:

sealed class Response<out T> {
    class Loading<out T>: Response<T>()

    data class Success<out T>(
        val data: T
    ): Response<T>()

    data class Failure<out T>(
        val errorMessage: String
    ): Response<T>()
}

Assuming that you have a "Post" class, let's create in the repository class the following function:

fun getPostsFromFirestore() = flow {
    emit(Loading())
    emit(Success(queryPostsByTimestamp.get().await().documents.mapNotNull { doc ->
        doc.toObject(Post::class.java)
    }))
}. catch { error ->
    error.message?.let { errorMessage ->
        emit(Failure(errorMessage))
    }
}

So we'll emit an object according to the state. When first-time calling the function, we emit a loading state using emit(Loading(), when we get the data we emit the List<Post> and if we get an error, we emit the error message using Failure(errorMessage).

Now we need to call this function, from the ViewModel class:

fun getPosts() = liveData(Dispatchers.IO) {
    repository.getPostsFromFirestore().collect { response ->
        emit(response)
    }
}

With the above function, we collect the data that we get from the getPostsFromFirestore() function call, and we emit the result further as a LiveData object so it can be observed in the activity/fragment like this:

private fun getPosts() {
    viewModel.getPosts().observe(this, { response ->
        when(response) {
            is Loading -> //Load a ProgessBar
            is Success -> {
                val postList = response.data
                //Do what you need to do with your list
                //Hide the ProgessBar
            }
            is Failure -> {
                print(response.errorMessage)
                //Hide the ProgessBar
            }
        }
    })
}

That's pretty much of it!

Upvotes: 8

broot
broot

Reputation: 28382

I don't know Firebase, so I may miss something, but generally speaking you don't need a special support in the library to use it with coroutines. If you start a background coroutine and then execute your above code in it, then Firebase will probably run within your coroutine without any problems.

The only problematic part could be listeners. Some libs invoke callbacks in the thread that was used to execute them, but some dispatch callbacks to a specific thread. In the case of Firebase it seems by default it runs listeners in the main thread. If this is not what you want, you can pass an executor to run callbacks within coroutines as well, e.g.:

.addOnSuccessListener(Dispatchers.Default.asExecutor()) { ... }

Upvotes: 0

Related Questions