Parallel request with Retrofit, Coroutines and Suspend functions any request 401 after we call refresh token, update access token then call the same request again,
class ServiceInterceptor() : Interceptor {
fun getSharedPreferences(): SharedPreferences {
return PreferenceHelper.customPrefs(TelioEVApp.appContext, "SHARED_DB")
}
private fun getAccessToken(): String? {
val sharedPreferences = PreferenceHelper.customPrefs(TelioEVApp.appContext, "SHARED_DB")
val token: String? = sharedPreferences.getString(PREF_ACCESS_TOKEN, "")
return token?.trim() ?: ""
}
override fun intercept(chain: Interceptor.Chain): Response {
val tokenManager = object : TokenManager {
val sharedPreferences =
PreferenceHelper.customPrefs(TelioEVApp.appContext, "SHARED_DB")
override fun getToken(): String? = getAccessToken()
override fun hasToken(): Boolean? {
if (getToken().isNullOrBlank()) {
return false
}
return true
}
override fun clearToken() {
sharedPreferences.setData(PREF_ACCESS_TOKEN, "")
}
override fun refreshToken(): String? {
val refreshToken: String? =
sharedPreferences.getData(Constants.PREF_REFRESH_TOKEN, "")
val client = RetrofitBuilder.getUnsafeOkHttpClient()
val params = JSONObject()
val body: RequestBody = RequestBody.create(
"application/json".toMediaTypeOrNull(),
params.toString()
)
val nRequest = Request.Builder()
.post(body)
.header("accept", "application/json")
.header("X-Device-ID", "ANDROID")
.header("Content-Type", "application/json-patch+json")
.header("refresh_token", "$refreshToken")
.url("${BuildConfig.BASE_URL}${BuildConfig.BASE_PORT}/api/token/refresh")
.build()
val response = client?.newCall(nRequest)?.execute()
if (response?.code == 200) {
// Get response
val jsonData = response.body?.string() ?: ""
val gson = Gson()
val loginResponse: ResponseBodies.LoginResponse = gson.fromJson(
jsonData,
ResponseBodies.LoginResponse::class.java
)
if (loginResponse.statusCode == 200) {
sharedPreferences.setData(
Constants.PREF_ACCESS_TOKEN,
loginResponse.body?.accessToken
)
sharedPreferences.setData(
Constants.PREF_REFRESH_TOKEN,
loginResponse.body?.refreshToken
)
sharedPreferences.setData(
Constants.PREF_EXPIRES_IN,
loginResponse.body?.expireIn
)
// Add refreshToken expires to shared preferences
sharedPreferences.setData(
Constants.PREF_REFRESH_TOKEN_EXPIRES,
loginResponse.body?.refreshExpireIn
)
// add user delta time to shared preferences
sharedPreferences.setData(
Constants.PREF_USER_DELTA_TIME,
System.currentTimeMillis() / 1000
)
} else {
val shareViewModel = ViewModelProvider(TelioEVApp.getInstance()).get(SharedViewModel::class.java)
shareViewModel.onLogout(ShareDataBean(1,loginResponse.message?:"Session expired"))
}
}
return getAccessToken()
}
}
val request = chain.request()
val apiRequest = request.newBuilder()
.addHeader("X-Device-ID", "ANDROID")
.apply {
if (!getAccessToken().isNullOrBlank())
header("Authorization", "Bearer " + getAccessToken())
}
.build()
// request.header("Authorization:Bearer" + getAccessToken())
val response = chain.proceed(apiRequest)
var unauthorized = false
if (response.code == 401 || response.code == 422) {
response.close()
unauthorized = true
}
if (unauthorized) {
tokenManager.clearToken() // remove old access token
tokenManager.refreshToken() // hit new api
val accessToken : String? = tokenManager.getToken()
if (!accessToken.isNullOrBlank()) {
val modifiedRequest = request.newBuilder()
.addHeader("X-Device-ID", "ANDROID")
.addHeader("Authorization", "Bearer $accessToken")
.build()
return chain.proceed(modifiedRequest)
}
}
return response
}
}
ServiceInterceptor.kt
lifecycleScope.launch {
try {
itemIds.asFlow()
.flowOn(Dispatchers.IO)
.collect{ itemId -> itemById[itemId] = MyService.getItem(itemId)}
itemIds.asFlow()
.flowOn(Dispatchers.IO)
.collect{ itemId -> itemById[itemId] = MyService.getItem(itemId)}
} catch (exception: Exception) {
exception.printStackTrace()
}
Log.i(TAG, "All requests have been executed")
}
Also please look at the lifecycle scope how to check refresh token call when any API 401 than call same request again