scenario : Let’s say there is an operation that takes an input value and, based on that value, returns an output value. In some cases, the operation may fail and throw an exception. now we can use flow that emit(input value) and by collect() we can get output value .
If the operation fails, an exception is thrown and we cannot call collect(), causing the app to crash.
Therefore, when we detect operation failure, we display a dialog box to the user, prompting them to choose between retrying or not. If the user selects retry, we proceed with the retry; otherwise, we do not
class Dispatch{
// Actual implementation of retry
fun Flow<Int>.retry(
retries: Long = Long.MAX_VALUE,
predicate: suspend (cause: Throwable) -> Boolean = {true}
): Flow<Int> {
require(retries > 0) {
"Expected positive amount of retries, but had $retries"
}
return retryWhen { cause, attempt ->
attempt < retries && predicate(cause)
}
}
fun Flow<Int>.retryWhen(
predicate: suspend FlowCollector<Int>.(
cause: Throwable,
attempt: Long,
) -> Boolean,
): Flow<Int> = flow {
var attempt = 0L
do {
val shallRetry = try { // say retry point
collect {
println("collect : emit($it)")
emit(it)
}
// it will only return false if there is no exception that flow sent
false
} catch (e: Throwable) { // say catch point
println("cause block , attempt : $attempt")
//val res = showDialogBox
// if( res == "Ok") --> then pass attempt++ otherwise attempt
predicate(e, attempt++) // we check if exception can be ignore and attempt < retries
.also {
if (!it) //if attempt > retries --> throw exception that we caught in collect // app crash
{
//throw e // Say critical point // do something
}
//else // again retry
}
}
} while (shallRetry)
}
// T Flow
fun interface FlowCollector<Int>{
suspend fun emit(value : Int)
}
interface Flow<Int>{
suspend fun collect(collector : FlowCollector<Int>)
}
fun flow(builder : suspend FlowCollector<Int>.() -> Unit): Flow<Int> = object : Flow<Int>{
override suspend fun collect(collector : FlowCollector<Int>){
collector.builder()
}
}
suspend fun main(){
flow {
emit(1)
emit(2)
throw IllegalStateException("")
emit(3)
}.retry(1) {
print(it.message)
true
}
.collect { print(it) }
}
}
where an exception is thrown within the flow{}
, but it catch in catch{} of retry and the actual throwing occurs within the if
block or catch
block of retryWhen()
, I refer to it as a critical juncture. At this juncture, if we opt not to throw an exception, despite doing so in the flow{}
, the application will not crash.
In the catch{}
block of retry()
, catching an exception signifies a failed operation. Subsequently, a dialog box is displayed to the user. If the user chooses to retry by pressing “OK”, the attempt count within retryWhen()
remains at 0, permitting another retry. Conversely, if the user declines, we handle the situation in a manner that avoids throwing an exception within the if(){}
or catch(){}
block of retry()
, thus preventing a crash in the application.
Is it the correct way to handle the problem? If not, please provide an alternative implementation to address it.