Kotlin Flows — Backoff and Retry Strategy

shivakumar
3 min readAug 7, 2023

--

In this blog we will learn how to implement back-off and retry request with Kotlin flows.

Getting Started

Let’s consider a situation where certain API request might be needed to retry when an Exception occurs i.e either server side error/network error without user input.

Let’s the example of normal IO exception which is pretty common. Consider a case where you want to fetch list of items from server and exception occurs.

Now either you can retry immediately or wait for sometime ie backoff and retry. We will explore both of these below.

When we talk about retry there are 2 ways to do it with flows.

  1. retryWhen
  2. retry

Let’s take a closer look at both of these operators as they can be widely used the same way.

retryWhen

Let’s look at retryWhen function source code which takes predicate to decide whether retry should be done or not.

public fun <T> kotlinx.coroutines.flow.Flow<T>.retryWhen(
predicate: suspend kotlinx.coroutines.flow.FlowCollector<T>.(kotlin.Throwable, kotlin.Long) -> kotlin.Boolean): kotlinx.coroutines.flow.Flow<T>

Actual implementation example code is shown below -

.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000) //static delay to not overload the server
return@retryWhen true
} else {
return@retryWhen false
}
}

The retryWhen function takes 2 paraments ie cause and attempt.

  1. cause is of type Throwable which is parent of error class.
  2. attempt is of type Long which takes number as the number attempts it has failed.

If the predicate returns true only then it will retry the API request, else it will exit the flow.

Viewmodel example -

flow {
emit(API call)
}.onEach { response ->
// Update UI
}
}.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000)
return@retryWhen true
} else {
return@retryWhen false
}
}.catch {
// Update Error UI
}.launchIn(viewModelScope)

retry

Let’s look at retry function source code which takes retries and predicate to decide whether retry should be done or not.

public fun <T> kotlinx.coroutines.flow.Flow<T>.retry(
retries: kotlin.Long,
predicate: suspend (kotlin.Throwable) -> kotlin.Boolean
): kotlinx.coroutines.flow.Flow<T>

The retry function takes 2 parameters ie retries and predicate.

Things to keep in mind

  1. If retries value is not given then it takes value as Long.MAX_VALUE.
  2. If predicate is missing then it returns true by default.

Let’s look at actual implementation example code with some examples

— Without any parameters if retry is done then it will keep sending the request until it is completed successfully.

retry()

— With retries value given as 3, the retry will happen only 3 times.

retry(retries = 3)

— With retries and predicate given it becomes similar to retryWhen. When predicate return true it is retried, else it exits the flow.

.retry(retries = 3) {cause ->
if(cause is IOException) {
delay(2000) // static delay
return@retry true
} else return@retry false
}

Viewmodel example -

flow {
emit(API call)
}.onEach { response ->
// Update UI
}
}.retry(retries = 3) {cause ->
if(cause is IOException) {
delay(2000)
return@retry true
} else return@retry false
}.catch {
// Update Error UI
}.launchIn(viewModelScope)

With all of the retry operations shown above we had used static delay as some backoff strategy. Now let’s look at exponential backoff and how it can be achieved.

Exponential Backoff

Let’s take the example of retry code and add exponential delay and see how it helps.

var currentDelay = 2000L
val delayFactor = 3
flow {
emit(API call)
}.onEach { response ->
// Update UI
}
}.retry(retries = 3) {cause ->
if(cause is IOException) {
delay(currentDelay)
currentDelay *= delayFactor
return@retry true
} else return@retry false
}.catch {
// Update Error UI
}.launchIn(viewModelScope)
  • currentDelay represents delay to be used in retry.
  • delayFactor is constant we multiply with currentDelay to increase delay for next API call.

That’s all for now. Thanks.

Please feel free to reach out to me on LinkedIn.

If you liked this blog, don’t forget to hit the 👏 .

--

--