Design Patterns: Retry /w Kotlin
Dealing with transient failures or intermittent issues using Retry Design Pattern in Kotlin.

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
Dealing with transient failures or intermittent issues using Retry Design Pattern in Kotlin.

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
If you dont immediately prompt the user that a network error has happened, you are sacrificing the perception of the app for the network. You are delaying a conclusion for the user. That the network is not working. Someone from the help desk will call you some day and will tell you he has users that are looking at progresbars or empty screens. And you will do some digging and you will see the app is retrying 3 times and there is a 12 second timeout on each request. User will not even stay on that screen for that long! It will then hit you how appropriate is after one timed out request to promt the user with an informative message and have him click retry if he wants to. The perception that some request fails just once rarely is false. Some people may work in a building that has a poor router. Or go to a bar that has a poor or crowded router and hang around. 3 out of 5 requests may consistently time out in that place indefinitely. IMHO Inform the user fast a request has failed and dont test his patience. This may get them upset! He wont be upset to manually retry with one tap.
I agree with your point. But this article is focused on Retry Design Pattern with some basic implementation and the implementation depends on dev's use case.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them β in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

In software development, dealing with transient failures or intermittent issues is a common challenge. One effective way to address this challenge is by implementing retry mechanisms. Retry design patterns enable developers to automatically retry an operation that has failed, with the hope that subsequent attempts will succeed. In this article, we will explore various retry design patterns in Kotlin, their implementation, and when to use them.
Transient Failures: Some failures are temporary and may be resolved with subsequent attempts.
Network Issues: Connection problems or timeouts might be resolved by retrying.
External Dependencies: When relying on external services, occasional failures are inevitable.
Fixed Retry: Retry a fixed number of times with a constant delay.
Exponential Backoff: Increase the delay exponentially with each retry.
Randomized Backoff: Introduce randomness in the retry delay to avoid synchronized retries.
fun <T> retry(
times: Int = 3,
initialDelay: Long = 1000,
maxDelay: Long = 10000,
factor: Double = 2.0,
block: () -> T
): T? {
var currentDelay = initialDelay
repeat(times - 1) {
try {
return block()
} catch (e: Exception) {
// Log or handle the exception
}
Thread.sleep(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
return block() // One last attempt without delay
}
implementation("io.github.benas.random-beans:random-beans:4.0.0")
val result: ResultType? = ExponentialBackoff()
.retryOn(Exception::class.java)
.maxRetries(3)
.intervalFunction { attempt -> 1000 * 2.0.pow(attempt.toDouble()).toLong() }
.call { yourFunction() }
fun fetchData(): String {
return retry {
// Code to fetch data
throw RuntimeException("Network error")
} ?: throw RuntimeException("Failed after multiple attempts")
}
fun fetchDataWithExponentialBackoff(): String {
return ExponentialBackoff<String>()
.retryOn(Exception::class.java)
.maxRetries(3)
.intervalFunction { attempt -> 1000 * 2.0.pow(attempt.toDouble()).toLong() }
.call {
// Code to fetch data
throw RuntimeException("Network error")
} ?: throw RuntimeException("Failed after multiple attempts")
}
Here is a simple example /w RetryHelper uses a function that operates with retry logic.
import kotlinx.coroutines.delay
import kotlin.random.Random
class RetryHelper(
private val maxRetries: Int = 3,
private val initialDelayMillis: Long = 100,
private val maxDelayMillis: Long = 1000
) {
suspend fun <T> retry(
operation: suspend () -> T
): T {
var currentRetry = 0
var delayMillis = initialDelayMillis
while (true) {
try {
return operation()
} catch (e: Exception) {
if (currentRetry >= maxRetries) {
throw e
}
// Exponential backoff with jitter
val backoffTime = delayMillis + Random.nextLong(-delayMillis / 2, delayMillis / 2)
delay(backoffTime)
currentRetry++
delayMillis = minOf(delayMillis * 2, maxDelayMillis)
}
}
}
}
suspend fun performNetworkRequest(): String {
// Simulating a network request that might fail
if (Random.nextBoolean()) {
throw RuntimeException("Network request failed")
}
return "Successful response"
}
// Example of using the RetryHelper
suspend fun main() {
val retryHelper = RetryHelper()
val result = retryHelper.retry {
// Your operation that might fail
performNetworkRequest()
}
println("Result: $result")
}
In this example, the RetryHelper class provides a retry function that takes a suspend function operation. The retry function attempts to execute the operation and catches any exceptions. If an exception occurs, it checks if the maximum number of retries (maxRetries) has been reached. If not, it applies an exponential backoff with jitter and retries the operation after a delay.
The main function demonstrates how to use the RetryHelper to perform a network request using the performNetworkRequest function. We can customize the parameters of the RetryHelper (e.g., maxRetries, initialDelayMillis, maxDelayMillis) based on our specific requirements.
This example uses Kotlin coroutines for asynchronous programming, and the delay function is used to introduce a delay between retry attempts. Adjust the parameters and error-handling logic according to our application's needs.
Retry design patterns are essential for creating robust and resilient systems, especially when dealing with external dependencies and network operations. Kotlin, with its concise syntax and powerful features, allows developers to easily implement and customize retry strategies. Whether using simple higher-order functions or dedicated libraries, understanding and applying retry patterns can significantly improve the reliability of our applications. Consider the nature of our application and the specific requirements when choosing a retry strategy.
That's it for today. Happy Coding...