plr
06/24/2020, 4:03 PMsealed class Result
data class Success(val image: Image) : Result()
object Error : Result()
class Image
And we have a suspending function to fetch an Image, which can throw an exception:
suspend fun fetchImage(url: String): Image {
// Returns either an Image instance or throws an exception
}
So we wrap this nasty function in another function which we'll use in our flows:
suspend fun fetchResult(url: String): Result {
return try {
val image = fetchImage(url)
Success(image)
} catch (e: IOException) {
Error
}
}
Finally, we can use it:
val urlFlow = flowOf("url1, url2", "retry")
val resultFlow = urlFlow.map { url -> fetchResult(url) }
So far, so good.
However, out of curiosity, I tried to re-write the same flow above using the catch operator:
val resultFlow: Flow<Result> = urlFlow.map { url ->
val image = fetchImage(url)
Success(image)
}.catch { e ->
emit(Error)
}
This code does not compile. I try to emit an Error from catch (even if it's a valid Result instance). The compiler says: "Type mismatch, required Success".
I know that it's because the inferred type of the upstream flow before catch is Flow<Success>.
But in my humble opinion, we should be able to write some similar logic with a flow of some super type, and FlowCollectors being able to emit subclasses of the sealed class.
Am I missing something?araqnid
06/24/2020, 4:21 PMmap
operator to produce a Flow<Result>
rather than a Flow<Success>
, e.g. urlFlow.map<Result> { … }
Zach Klippenstein (he/him) [MOD]
06/24/2020, 4:38 PMaraqnid
06/24/2020, 4:57 PMmap
takes two type parameters, it would have to be map<String,Result> { … }
Success(image) as Result
which I admit looks a bit weirdKroppeb
06/24/2020, 5:10 PMplr
06/24/2020, 5:15 PMaraqnid
06/24/2020, 7:02 PMFlow<out T>
Result
for that catch
extension call, but apparently not.Kroppeb
06/24/2020, 7:04 PMResult
as a type Param for catch
?araqnid
06/24/2020, 7:11 PMmap
fun <T> Flow<T>.catchAs(mapper: (Throwable) -> T): Flow<T> = catch { emit(mapper(it)) }
and then:
val flow = flowOf("x")
.map { value -> Outcome.Success("It was $value") }
.catchAs { Outcome.Failure(it) }
Zach Klippenstein (he/him) [MOD]
06/24/2020, 7:53 PMcatch
could handle this case if it had another type parameter. That might be worth filing/submitting a PR for (not just for catch, but in general).
The current signature is:
fun <T> Flow<T>.catch(
action: suspend FlowCollector<T>.(cause: Throwable) -> Unit
): Flow<T>
But if it were this, then the compiler would be able to infer that T
is Success
but R
must be the lowest-common-supertype of Success
and Error
, and your code would work as expected:
fun <T : R, R> Flow<T>.catch(
action: suspend FlowCollector<R>.(cause: Throwable) -> Unit
): Flow<R>
There are probably a lot of operators that could use that pattern.Kroppeb
06/24/2020, 7:54 PMflow<out T>
flow<T>
is also flow<R>
araqnid
06/24/2020, 8:15 PMFlow<Outcome>
Zach Klippenstein (he/him) [MOD]
06/24/2020, 8:27 PM@BuilderInference
annotation now, since it’s actually doing active inference.araqnid
06/24/2020, 8:31 PM