Reputation: 3232
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
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
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