Fahad Bilal
Fahad Bilal

Reputation: 159

multiple async-await in kotlin

obj in promoType = [list of string] its more like 10 firebase queries are running here, looking in 10 particular set of nodes and going down further.

what i'm not sure, whether i require to put on async / await on each of the queries, but all i want is 10 of these queries to run and then result me in whether a couponKey is empty or not. All i want to do is to display whether a coupon entered was correct or not.

further, in changeUserType(couponKey, couponFoundAtKey), some database write operations occur.

fun checkPromo(promoCodeET: String) = async(UI) {
    try {
        val database = PersistentFirebaseUtil.getDatabase().reference
        val job = async(CommonPool) {

            for (obj in promoType) {
                val query = database.child("promos").child(obj).orderByChild("promoCode").equalTo(promoCodeET)

                query.addListenerForSingleValueEvent(object :
                        ValueEventListener {
                    override fun onDataChange(dataSnapshot: DataSnapshot) {
                        if (dataSnapshot.exists()) {
                            couponKey = dataSnapshot.key.toString()
                            couponFoundAtKey = dataSnapshot.children.first().key.toString()
                            if (couponKey.isNotEmpty())
                                changeUserType(couponKey, couponFoundAtKey)
                            flag = true
                        }
                    }

                    override fun onCancelled(error: DatabaseError) {
                        // Failed to read value
                    }
                })
                if (flag) break
            }
        }
        job.await()            

    }
    catch (e: Exception) {
    }
    finally {
        if (couponKey.isEmpty()){
            Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
        }
        flag = true
    }
}

Upvotes: 1

Views: 4270

Answers (1)

Marko Topolnik
Marko Topolnik

Reputation: 200138

There are several things I find wrong with your code:

  1. You have an outer async(UI) which doesn't make sense
  2. Your inner async(CommonPool) doesn't make sense either, because your database call is already async
  3. You use the antipattern where you immediately await after async, making it not really "async" (but see above, the whole thing is async with or without this)
  4. Your fetching function has a side-effect of changing the user type
  5. To transfer the results to the caller, you again use side-effects instead of the return value

Your code should be much simpler. You should declare a suspend fun whose return value is the pair (couponKey, coupon):

suspend fun fetchPromo(promoType: String, promoCodeET: String): Pair<String, String>? =
    suspendCancellableCoroutine { cont ->
        val database = PersistentFirebaseUtil.getDatabase().reference
        val query = database.child("promos").child(promoType)
                .orderByChild("promoCode").equalTo(promoCodeET)
        query.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                cont.resume(
                    dataSnapshot
                        .takeIf { it.exists() }
                        ?.let { snapshot ->
                            snapshot.key.toString()
                                .takeIf { it.isNotEmpty() }
                                ?.let { key ->
                                    Pair(key, snapshot.children.first().key.toString())
                                }
                        }
                )
            }

            override fun onCancelled(error: DatabaseError?) {
                if (error != null) {
                    cont.resumeWithException(MyException(error))
                } else {
                    cont.cancel()
                }
            }
        })
    }

To call this function, use a launch(UI) at the call site. Change the user type once you get a non-null value:

launch(UI) {
    var found = false
    for (type in promoType) {
        val (couponKey, coupon) = fetchPromo(type, "promo-code-et") ?: continue
        found = true
        withContext(CommonPool) {
            changeUserType(couponKey, coupon)
        }
        break
    }
    if (!found) {
        Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
    }
}

You say that changeUserType performs some database operations, so I wrapped them in a withContext(CommonPool).

Note also that I extracted the loop over promo types outside the function. This will result in queries being performed sequentially, but you can just write different calling code to achieve parallel lookup:

var numDone = 0
var found = false
promoType.forEach { type ->
    launch(UI) {
        fetchPromo(type, "promo-code-et")
            .also { numDone++ }
            ?.also { (couponKey, coupon) ->
                found = true
                launch(CommonPool) {
                    changeUserType(couponKey, coupon)
                }
            }
            ?: if (numDone == promoType.size && !found) {
                Toast.makeText(this@Coupon, "Invalid coupon", Toast.LENGTH_LONG).show()
            }
    }
}

Upvotes: 3

Related Questions