An easy way to integrate the HTTP 429 feature using RetrofitRetry

More about rate limits

RetrofitRetry basic usage

  1. In contrast to other network libraries, Retrofit allows you to configure your code quickly.
  2. It is type-safe.
  3. Retrofit supports Kotlin coroutines.
  4. This library shows good extensibility.
allprojects { 
repositories {
// google(), jcenter()...
maven { url 'https://jitpack.io' }
}
}
dependencies { 
implementation "com.lembergsolutions:retrofitretry:1.0.0"
// ...
}
private val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(
RetrofitRetryCallAdapterFactory.createCoroutineAdapter())
.build()
.create(ApiInterface::class.java)
interface ApiInterface {
@RetryOnError
@GET("/news")
suspend fun fetchNews(): Response<ResponseData>
}
Retry-After: 5
Retry-After: Thu, 24 Feb 2022 04:30:00 EET

RetrofitRetry advanced usage

interface ApiInterface {
@RetryOnError(maxRetries = 10, handlerClass =
MyCustomRetryHandler::class)
@GET("/news")
suspend fun fetchNews(): Response<ResponseData>
}
/** 
* Get the delay to wait before retrying next request
* @param request request object
* @param result request result containing response or error
* @param retryCount current retry count
* @param maxRetries maximum retries set in annotation
* @return >= 0 delay before retry or -1 to cancel retrying
*/
fun getRetryDelay(
request: Request,
result: Result<retrofit2.Response<out Any?>>,
retryCount: Int,
maxRetries: Int): Long
class Http429RetryHandler : RetryHandler {
private val httpDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US)

override fun getRetryDelay(request: Request, result: Result<retrofit2.Response<out Any?>>, retryCount: Int, maxRetries: Int): Long {
if (result.isSuccess) {
result.getOrNull()?.let { response ->
if (!response.isSuccessful && response.code() == HTTP_TOO_MANY_REQUESTS_STATUS) {
val delayMillis = response.headers()[RETRY_AFTER_HEADER]?.let { tryParseDelayBySeconds(it) ?: tryParseDelayByHttpDate(it) }
if (delayMillis != null) return delayMillis
}
}
}
// don't repeat
return -1
}

private fun tryParseDelayBySeconds(str: String): Long? {
return try {
TimeUnit.SECONDS.toMillis(str.toLong())
} catch (e: Exception) {
null
}
}

private fun tryParseDelayByHttpDate(str: String): Long? {
val repeatTimeMillis = tryParseHttpDate(str) ?: return null
val currentTimeMillis = Calendar.getInstance().timeInMillis
if (repeatTimeMillis >= currentTimeMillis) return repeatTimeMillis - currentTimeMillis
return null
}

private fun tryParseHttpDate(str: String): Long? {
return try {
httpDateFormat.parse(str).time
} catch (e: Exception) {
null
}
}

companion object {
const val HTTP_TOO_MANY_REQUESTS_STATUS = 429
const val RETRY_AFTER_HEADER = "Retry-After"
}
}
interface DelayRunner {
/**
* Schedule function execution after specified delay.
* Any previously scheduled function will be cancelled.
* @param millis delay in milliseconds
* @param fn function to call
*/
fun scheduleDelayedRun(millis: Long, fn: () -> Unit)

/**
* Cancel previously scheduled delayed run.
*/
fun cancelDelayedRun()
}
private val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(
RetrofitRetryCallAdapterFactory.createCustomAdapter(myDelayRunner))
.build()
.create(ApiInterface::class.java)

Wrapup

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store