Reputation: 1654
I have a consumer that looks like this:
// ItemConsumer.kt
try {
job = itemService
.connect()
.flowOn(Dispatchers.IO)
.catch { e ->
throw e
}
.onEach {
// Update UI for each item collected
}
.launchIn(viewModelScope)
} catch (e : Exception) {
// Handle exception
}
And I have a producer that looks this:
// ItemService.kt (Producer)
fun connect():Flow<Item> = callbackFlow {
check(1 == 0) // This is expected to throw an IllegalStateException
}
I understand that the .catch { } will handle any issues that would arise inside the Consumer's .collect { } or the .onEach { } block. But how can I throw the exception that happens inside the callbackFlow { } builder so that it may be caught in the .catch { } of the consumer?
Upvotes: 2
Views: 2385
Reputation: 11978
catch
operator catches only upstream exceptions (that is an exception from all the operators above catch, but not below it). In your code, any exception in onEach
or collect
(via launchIn
) won't be catched. You need to move this operator below onEach
to catch all exceptions.
This operator catches exceptions in the flow completion. It could be possible to catch them in callbackFlow
builder only if the flow completes normally or exceptionally. As @SomeRandomITBoy said it will never be thrown by the flow builder. But since this builder uses a Channel under the hood, in order to complete the flow exceptionally, you have to close
it manually with non-null cause:
abstract fun close(cause: Throwable? = null): Boolean
Keep in mind that might violates transparency: "Flows must be transparent to exceptions and it is a violation of the exception transparency to emit values in the flow { ... }
builder from inside of a try/catch
block". I might wrong but I think, since you use catch
, this "preserves this exception transparency and allows encapsulation of its exception handling".
So the consumer could be as follows:
job = itemService.connect()
.flowOn(Dispatchers.IO)
.onEach {
// update UI
}
.catch { e ->
// handle exception
}
.launchIn(viewModelScope)
And the producer should close with an exception like this:
fun connect(): Flow<Item> = callbackFlow {
try {
check(1 == 0) // throws an exception
} catch (e: Exception) {
close(e) // close manually with exception
}
}
Upvotes: 4
Reputation: 8457
You can't. The exception will never be thrown by the flow builder function. Flow
s are cold, meaning that no code is executed until collect is called. You'll always build a flow successfully even if the execution of collection yields to an exception.
Upvotes: 0