jsamol
jsamol

Reputation: 3232

Result nested twice when collecting a Flow in Kotlin 1.5 (kotlin.Result cannot be cast to `Type`)

Recently I've updated my project to Kotlin 1.5 and noticed some weird and unexpected behaviour when combining Flow with Result. Consider the following example:

// Kotlin 1.5.0
// Coroutines (kotlinx-coroutines-core) 1.5.0

class Foo

interface FooListener {
    fun onSuccess(foo: Foo)
    fun onFailure(error: Throwable?)
}

suspend fun collectData(listener: FooListener) {
    flow { emit(Result.success(Foo())) }
        .collect { result -> // for reference
            when {
                result.isSuccess -> listener.onSuccess(result.getOrThrow())
                result.isFailure -> listener.onFailure(result.exceptionOrNull())
            }
        }
}

fun main() {
    runBlocking {
        collectData(object : FooListener {
            override fun onSuccess(foo: Foo) {
                println(foo)
            }

            override fun onFailure(error: Throwable?) {
                println(error)
            }
        })
    }
}

The above compiles but fails in runtime with:

Exception in thread "main" java.lang.ClassCastException: kotlin.Result cannot be cast to Foo

After some debugging I noticed that the collected value (result) appears to be of type Result<Result<Foo>> instead of the expected Result<Foo>:

suspend fun collectData(listener: FooListener) {
    flow { emit(Result.success(Foo())) }
        .collect { result ->
            println(result) // Output: Success(Success(Foo@...))
            when {
                result.isSuccess -> listener.onSuccess(result.getOrThrow())
                result.isFailure -> listener.onFailure(result.exceptionOrNull())
            }
        }
}

The problem disappears when I give up an idea of using externally provided listeners or callback functions in collectData and, for example, use something defined globally:

class Foo

interface FooListener {
    fun onSuccess(foo: Foo)
    fun onFailure(error: Throwable?)
}

val listener = object : FooListener {
    override fun onSuccess(foo: Foo) {
        println(foo)
    }

    override fun onFailure(error: Throwable?) {
        println(error)
    }
}

suspend fun collectData() {
    flow { emit(Result.success(Foo())) }
        .collect { result ->
            println(result) // Output: Success(Foo@...)
            when {
                result.isSuccess -> listener.onSuccess(result.getOrThrow())
                result.isFailure -> listener.onFailure(result.exceptionOrNull())
            }
        }
}

fun main() {
    runBlocking {
        collectData()
    }
}

The above runs without any errors.


At this point it seems to me like a bug in the language, but I've been onto this for so long I'm not 100% sure and I could use a second opinion before bringing it up on Kotlin YouTrack.

I've seen Why does Kotlin crash passing generic Result parameter? and JVM / IR: ClassCastException with Result object when it is used by a generic method in a suspend call but the bug not only looks the other way around, but it is supposed to be fixed in the newest version as well.

Upvotes: 4

Views: 2859

Answers (2)

Eliezer
Eliezer

Reputation: 7347

This is tracked in https://youtrack.jetbrains.com/issue/KT-46915 and looks like the fix will be released with Kotlin 1.5.30

Upvotes: 2

EL TEGANI MOHAMED
EL TEGANI MOHAMED

Reputation: 1898

I have The same issue I downgraded the kotlin version from 1.5.10 to 1.4.32 and the code run correctly.

Upvotes: 1

Related Questions