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.