1

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
    }
}

1 Answer 1

3

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()
            }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

this is like simplicity-heaven in your answer! can you please refer to me more on where could i read on launch(UI) & .also , .takeIf ?
.also and .takeIf are extension functions available for any type, including null. They are a part of the standard library and an essential aspect of idiomatic Kotlin code. I recommend you to study all these standard extension functions and try to use them everywhere, that way you'll learn when they provide value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.