简体   繁体   中英

how to avoid deeply nested callbacks in call FireStore (Firebase)'s api by kotlin coroutine

How can I prevent having deeply nested callbacks when using the Firebase/Firestore API?

Py app call firestore api step by step, and i need do something process when onSuccess and onFailed .

For example, it need 5 steps. For going to the next step, it needs reference to pre-call api's result.

1st Step: getA() // get A Data from firestore

2nd Step: if(resultGetA.isSuccess) getB() else updateC()

3rd Step: if(resultGetB.isSuccess) addD()

4th Step: if(resultAddD.isSuccess) updateE()

5th Step: if(resultUpdateE.isSuccess) getF()


example to kotlin code

This is just an example source for explain my question, but my application's code similar like this:(

fun callHellExample1(email:String, pass:String, model:UserDataModel) {
        val collectRef = Firebase.firestore.collection("A")
        val auth = Firebase.auth

        auth.createUserWithEmailAndPassword(email, pass).addOnCompleteListener { createTask ->
            if (createTask.isSuccessful) {
                auth.signInWithEmailAndPassword(email, pass).addOnCompleteListener { signInTask ->
                    if (signInTask.isSuccessful) {
                        collectRef.add(model).addOnCompleteListener {
                            Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
                        }
                    } else {
                        Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
                    }
                }
            } else {
                Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
            }
        }
    }


fun callHellExample2(callback : (Boolean)-> Unit) {
        val collectRef = Firebase.firestore.collection("A")
        val auth = Firebase.auth

        collectRef.document("A").get().addOnCompleteListener { resultA ->
            if(resultA.isSuccessful){
                collectRef.document("B").get().addOnCompleteListener { resultB ->
                    if(resultB.isSuccessful){
                        collectRef.add("D").addOnCompleteListener { resultD ->
                            if(resultD.isSuccessful){
                                collectRef.document("E").update("someFiled", "someValue").addOnCompleteListener { resultE ->
                                    if(resultE.isSuccessful){
                                        collectRef.document("F").get().addOnCompleteListener {
                                            auth.signOut()
                                            callback(true)
                                        }
                                    }
                                }
                            }
                        }
                    }else{
                        Toast.makeText(this, "getB ... isSuccessful? = ${resultB.isSuccessful}", Toast.LENGTH_SHORT).show()
                    }
                }
            }else{
                collectRef.document("C").update("someFiled", "someValue").addOnCompleteListener { resultC ->
                    Toast.makeText(this, "update C ... isSuccessful? = ${resultC.isSuccessful}", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

so i try use by coroutine. but i can't found escape callback hell about firestore api

i tried like this (example 1). but it similar like callback hell. i want check it successful.

await() return not Task<AuthResult> . it just return AuthResult but AuthResult is not contains isSuccessful variable

fun example1ByCoroutine(email:String, pass:String, mode:UserModel){
        CoroutineScope(Dispatchers.IO).launch {
            try{
                auth.createUserWithEmailAndPassword(email, pass).await()
                try{
                    auth.signInWithEmailAndPassword(email, pass).await()
                    try{
                        collectRef.add(model).await()
                        withContext(Dispatchers.Main){
                            Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
                        }
                    }catch (e: Exception){
                        Toast.makeText(this, "failed create account in Step 3", Toast.LENGTH_SHORT).show()
                    }
                }catch (e: Exception){
                    Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
                }
            }catch (e: Exception){
                Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
            }
        }
    }

example 2 is can not show toast cuz can not check isSuccessful too. is not return Task. it just return DocumentSnapShot

i look forward to your reply. thanks!

ps) if can access to isSuccessful, code can be edit like this

fun example1ByCoroutine(email:String, pass:String, mode:UserModel){
        CoroutineScope(Dispatchers.IO).launch {
            if(!auth.createUserWithEmailAndPassword(email, pass).await().isSuccessful){
                Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
                return@launch
            }

            if(!auth.signInWithEmailAndPassword(email, pass).await().isSuccessful){
                Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
                return@launch
            }
            
            if(collectRef.add(model).await().isSuccessful){
                Toast.makeText(this, "failed create account in Step 3", Toast.LENGTH_SHORT).show()
                return@launch
            }
                    
            withContext(Dispatchers.Main){
                Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
            }
        }
    }

I would not advice to re-implement the Task.await() method to return the task itself again, instead you can add a simple wrapper to the standard kotlin Result class:

import com.google.android.gms.tasks.Task
import kotlinx.coroutines.tasks.await

suspend fun <T> Task<T>.awaitResult() = runCatching { await() }

And then use it like this:

suspend fun foo(email: String, pass: String){
    if(auth.createUserWithEmailAndPassword(email, pass).awaitResult().isFailure){
        Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
        return
    }

    if(auth.signInWithEmailAndPassword(email, pass).awaitResult().isFailure){
        Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
        return
    }
}

Coroutines have integration with Google Play Services Tasks API .

Just add this dependency:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'

You can convert a Task into a Deferred using Task.asDeferred . Similarly, Task.await Awaits for completion of the Task (cancellable).

You can read more about it here: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM