4

I have a flow which could possible throw an error like so:

val myFlow = flow {
    emit("1")
    delay(2000)
    emit("2")
    delay(2000)
    emit("3")
    delay(2000)
    emit("4")
    delay(2000)
    throw Exception() // here it would throw an error
    delay(10000)
    emit("6")  // because the flow completes on error, it doesn't emit this
}

My problem is that, when the error is thrown even when I add a .catch { error -> emit("5") }.. it still completes the flow, and so "6" isnt emitted.

myFlow.catch { error ->
    emit("5")
}.onEach {
    println("$it")
}.onCompletion {
    println("Complete")
}.launchIn(scope)

And the result is:

1
2
3
4
5
Complete

I need it to be:

1
2
3
4
5
6
Complete

I want to swallow the error instead of making the flow complete. How can I achieve this?

Archie G. Quiñones
  • 11,638
  • 18
  • 65
  • 107

2 Answers2

2

OK, I know it's not exactly the same example but I suspect my case is somewhat similar.

Let`s say you have some kind of risky flow. The flow may throw an exception. But you want to maintain the connection with the flow even after exception, because it emits some important, e.g., server real-time updates.

And your goal is suppress an exception or convert it to some data and continue listening to the real-time updates no matter what.

    var counter = 0

    val riskyFlow = flow {

        while (true) {
            counter++

            delay(1000)

            if (counter == 3) 
                throw IllegalStateException("Oops! Error.")
            else 
                emit("Server update")
            
        }
    }

If you use catch, risky-flow will complete after you emit something you want on error.

riskyFlow
        .catch { cause -> emit("Emit on error") }
        .onEach { println(it) }
        .launchIn(GlobalScope)

Solution

Use retry or retryWhen. This way you suppress exception by emitting some data on exception, then you'll restart the connection to the flow right away, so it'll continue emitting its data.

riskyFlow
        .retryWhen { cause, attempt ->
            emit("emit on error")
            true
        }
        .onEach { println(it) }
        .launchIn(GlobalScope)

The output is:

I/System.out: Server update
I/System.out: Server update
I/System.out: emit on error
I/System.out: Server update
I/System.out: Server update
Andrew
  • 2,438
  • 1
  • 22
  • 35
0

This is not possible in your current example since the last 2 lines in your flow are unreachable.

you should deal with the exception inside of your flow meaning catch the exception in the flow and emit 5 in your example.

Like this

val myFlow = flow {
        emit("1")
        delay(2000)
        emit("2")
        delay(2000)
        emit("3")
        delay(2000)
        emit("4")
        delay(2000)
        try {
            throw Exception() // here it would throw an error
        } catch (e: Exception) {
            emit("5")
        }
        delay(10000)
        emit("6")  // because the flow completes on error, it doesn't emit this
    }
Sharon
  • 508
  • 4
  • 11
  • @ArchieG.Quiñones cold flows follow standard execution logic. Your question is equivalent to "how to continue to execute body of a method after exception is thrown". This is not possible because exception throwing breaks the execution, that's how JVM works. – Deinlandel Jan 18 '21 at 12:45
  • This is wrong. Since exception is catched in flow, flow does not complete and "6" is emitted. – mtw Sep 13 '22 at 14:27