I need to implement a catch when an error occur like network related or the API limit has been reached. Now I've seen a lot of good samples yet it seems they are both missing something.
First in this tutorial the handling of cache is based the network status like if a mobile data or Wi-Fi of the device is on/off. It is not the solution since one can be connected to a network but the network has no data or no internet at all.
Second, this one which seems a little verbose will just work at first as it seems I already cache the response before implementing his sample interceptors but stops working after I clear the app cache then try to run it with this code, giving me an error HTTP 504 Unsatisfiable Request (only-if-cached)
and does not give a fresh data every fetch.
So the question is the same but given the two tutorial, which one is the best suited? Probably the second one but it just need some changes.
The ideal flow on me would be
- Cache every time a fetch is success which repeats every 5 seconds.
- Use only the last cached data available if any of the performed fetch failed, if no error then use the fresh data set from online response.
- The cached data can be available for days or weeks and will only be updated again every new fetch is success.
- If no cache yet is available and the very first fetch was failed only then show the error.
My code
interface EndpointServices {
companion object {
private fun interceptor(): Interceptor {
return Interceptor { chain ->
var request: Request = chain.request()
val originalResponse: Response = chain.proceed(request)
val cacheControl: String? = originalResponse.header("Cache-Control")
if (cacheControl == null || cacheControl.contains("no-store") || cacheControl.contains("no-cache") ||
cacheControl.contains("must-revalidate") || cacheControl.contains("max-stale=0")
) {
Log.wtf("INTERCEPT", "SAVE A CACHE")
val cc: CacheControl = CacheControl.Builder()
.maxStale(1, TimeUnit.DAYS)
.build()
request = request.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public")
.cacheControl(cc)
.build()
chain.proceed(request)
} else {
Log.wtf("INTERCEPT", "ONLINE FETCH")
originalResponse.newBuilder()
.removeHeader("Pragma")
.build()
}
}
}
private fun onlineOfflineHandling(): Interceptor {
return Interceptor { chain ->
try {
Log.wtf("INTERCEPT", "TRY ONLINE")
chain.proceed(chain.request())
} catch (e: Exception) {
Log.wtf("INTERCEPT", "FALLBACK TO CACHE")
val cacheControl: CacheControl = CacheControl.Builder()
.maxStale(1, TimeUnit.DAYS)
.onlyIfCached() //Caching condition
.build()
val offlineRequest: Request = chain.request().newBuilder()
.cacheControl(cacheControl)
.build()
chain.proceed(offlineRequest)
}
}
}
fun create(baseUrl: String, context: Context): EndpointServices {
val cacheSize: Long = 10 * 1024 * 1024 // 10 MB
val cache = Cache(context.cacheDir, cacheSize)
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val httpClient = OkHttpClient.Builder()
.cache(cache)
.addInterceptor(interceptor)
.callTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor())
.addInterceptor(onlineOfflineHandling())
.build()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(
RxJava2CallAdapterFactory.create()
)
.addConverterFactory(
MoshiConverterFactory.create()
)
.client(httpClient)
.baseUrl(baseUrl)
.build()
return retrofit.create(EndpointServices::class.java)
}
}
Main activity
intervalDisposable = Observable.interval(0L, 5L, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Log.d("Interval", it.toString())
fetchAssets(UriUtil.assetField, "30")
}
private fun fetchAssets(field: String, limit: String) {
disposable = EndpointServices.create(url, requireContext()).getAssetItems(
field,
limit
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
//Our response here
},
{ error ->
//Error, offline and no cache has been found
Log.wtf("WTF", "${error.message}")
Toast.makeText(context, error.message, Toast.LENGTH_LONG).show()
}
)
}
I am working with @GET
UPDATES:
I tried to work with Youtube API as an example and this is what the test result.
Mobile Data/Wi-Fi off
- INTERCEPT: TRY ONLINE
- INTERCEPT: FALLBACK TO CACHE
- The response was return (it works!)
Wi-Fi on and connected to a network but has no data/internet connection service
- INTERCEPT: TRY ONLINE
- Waiting for response to timeout?
- INTERCEPT: FALLBACK TO CACHE
- No response was return (WTF?)
Mobile data/Wi-Fi on and internet service is available (Online) works on Youtube API only so far
- INTERCEPT: TRY ONLINE
- INTERCEPT: ONLINE FETCH
- The response was return (it works!)
I tried working on other APIs too but no luck so far only YouTube API works but not as intended yet. I need an approach that could work on almost any API.
from Retrofit2 and OkHttp3 use cache only when error occur such as Network errors or quota limit reach
No comments:
Post a Comment