3
votes

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
Why not check if it's null and return an empty list instead?m0skit0
is addAllNearbyLocations asynchronous?Animesh Sahu
@m0skit0 It should return non-null object. Checking the null-ability is not the problem I've here. The real problem is, It should not return a null value in the first place.Yekta Sarıoğlu
@Tenfour04 is correct, it is async. To bridge old async code that uses callbacks with coroutines check suspendCoroutine. I'll write a comprehensive answer.m0skit0
When a function from some library or API takes a callback parameter, it's because it's running some code on a background thread and then will call your callback when it's complete. This is the definition of asynchronous. So when you get to the line deferred?.await(), the callback has not been called yet. You can convert non-coroutine asynchronous library code into a suspend function using suspendCoroutine as @m0skit0's answer shows.Tenfour04

1 Answers

2
votes

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).