What would be the cases if Coroutine's Deferred type gets null?

Viewed 1233

I'm using nested Coroutine blocks in my code. And I'm getting a null value when I tried to get Deferred type's result to a variable. Thus, It causes a casting problem which is kotlin.TypeCastException: null cannot be cast to non-null type kotlin.collections.ArrayList in getNearbyHealthInstitutions() method's return line. I believe, I did the right implementation at some point but what am I missing to get null value from Deferred's result? The funny thing is when I debug it, it does return the expected value. I think it should be the concurrency problem or I don't have any idea why it works in debug mode in the first place. Any ideas fellas?

// Invocation point where resides in a callback
GlobalScope.launch(Dispatchers.Main) {
    nearbyHealthInstitutionSites.value = getNearbyHealthInstitutions()
}

private suspend fun getNearbyHealthInstitutions(radius: Meter = DEFAULT_KM_RADIUS) : ArrayList<Hospital> {
    return CoroutineScope(Dispatchers.IO).async {
        val list = getHealthInstitutions()
        val filteredList = list?.filter { it.city == state?.toUpperCase() } as MutableList<Hospital>
        Log.i(MTAG, "nearby list is $filteredList")
        Log.i(MTAG, "nearby list's size is ${filteredList.size}")

        var deferred: Deferred<MutableList<Hospital>>? = null

        addAllNearbyLocations(onEnd = { nearbyHealthInstitutions ->
            deferred = async {
                findNearbyOfficialHealthInstitutions(
                    officialHealthInstitutionList = filteredList as ArrayList<Hospital>,
                    nearbyHealthInstitutions = nearbyHealthInstitutions
                )
            }
        })

        val result = deferred?.await()

        return@async result as ArrayList<Hospital>
    }.await()
}

private suspend fun findNearbyOfficialHealthInstitutions(officialHealthInstitutionList: ArrayList<Hospital>, nearbyHealthInstitutions: MutableList<Hospital>): MutableList<Hospital> {
        return GlobalScope.async(Dispatchers.Default) {
            val result = mutableListOf<Hospital>()

            officialHealthInstitutionList.forEach {
                nearbyHealthInstitutions.forEach { hospital ->
                    StringSimilarity.printSimilarity(it.name, hospital.name)

                    val similarity = StringSimilarity.similarity(it.name, hospital.name.toUpperCase())

                    if (similarity > SIMILARITY_THRESHOLD) {
                        Log.i(MTAG, "findNearbyOfficialHealthInstitutions() - ${it.name} and ${hospital.name.toUpperCase()} have %$similarity")
                        result.add(hospital)
                    }

                }
            }

            Log.i(TAG, "------------------------------------------")
            result.forEach {
                Log.i(MTAG, "findNearbyOfficialHealthInstitutions() - hospital.name is ${it.name}")
            }

            return@async result
        }.await()
    }
1 Answers

Since addAllNearbyLocations() is asynchonous, your coroutine needs to wait for the callback to be called to continue its execution. You can use suspendCoroutine API for this.

val result = suspendCoroutine { continuation ->
    addAllNearbyLocations(onEnd = { nearbyHealthInstitutions ->
          findNearbyOfficialHealthInstitutions(
                officialHealthInstitutionList = filteredList as ArrayList<Hospital>,
                nearbyHealthInstitutions = nearbyHealthInstitutions
          ).let { found -> continuation.resume(found) }
    })
}

On a separate note you should use List instead of ArrayList or MutableList, you should always look to use a generic interface instead of a specific implementation of that interface. This also gets rids of some of the castings (ideally you should have no castings in this code).

Related