I am caching http response using okHttp, I didn't implemented anything in server side. so for caching I am using Interceptor. and I have set cache age to one week. so it works fine for one week, If I have changed the device time to more than one week then it is loads from the server and it is not taking from cache, what I need is after expiring the age it should take the new response from server and it should persist until expiring the cache. my Interceptor is given below.
private class CacheInterceptor implements Interceptor {
Context mContext;
public CacheInterceptor(Context context) {
this.mContext = context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
request = request.newBuilder()
.header(HEADER_SESSION_ID, sessionId)
.header(HEADER_MOBILE_OS, Constants.MOBILE_OS)
.build();
}
Response response = chain.proceed(request);
return response.newBuilder()
.header("Cache-Control", "public, max-age=" + CACHE_MAX_AGE)ONE_WEEK).build();
}
}
}
can any one guide me through this ?
Related
I read dozens of tutorial and Stackoverflow answers to my problem but nothing is working for me! Also, most of them are old so probably OKHTTP changed somehow.
All I want is to enable offline caching for Retrofit.
I am using GET
I tried using only offlineCacheInterceptor as an Interceptor, but I kept getting:
Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname
I tried using a combination of offlineCacheInterceptoras an Interceptor + provideCacheInterceptor() as a NetworkInterceptor, but I kept getting:
504 Unsatisfiable Request (only-if-cached) and a null response.body()
I even made sure to add .removeHeader("Pragma") everywhere!
I tried all these Links:
https://newfivefour.com/android-retrofit2-okhttp3-cache-network-request-offline.html (One interceptor, Not working!!)
https://medium.com/mindorks/caching-with-retrofit-store-responses-offline-71439ed32fda (One interceptor, Not working!)
https://caster.io/lessons/retrofit-2-offline-cache (Separate Online + Offline caching, Not working)
https://www.journaldev.com/23297/android-retrofit-okhttp-offline-caching (Not working, 504 Unsatisfiable Request (only-if-cached))
http://mikescamell.com/gotcha-when-offline-caching-with-okhttp3/ (One interceptor, Not working!!)
https://stackoverflow.com/a/48295397/8086424 (Not Working)
Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname
Can Retrofit with OKHttp use cache data when offline (TOO confusing!)
Here's my code:
public static Retrofit getRetrofitInstance(Context context) {
if (retrofit == null) {
c = context;
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(context.getCacheDir(), cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(provideHttpLoggingInterceptor())
.addInterceptor(offlineCacheInterceptor)
.addNetworkInterceptor(provideCacheInterceptor())
.cache(cache)
.build();
//////////////////////////
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
}
return retrofit;
}
public static Interceptor offlineCacheInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Log.e("bbbb", "bbbb");
if (!checkInternetAvailability()) {
Log.e("aaaaa", "aaaaaa");
CacheControl cacheControl = new CacheControl.Builder()
.maxStale(30, TimeUnit.DAYS)
.build();
request = request.newBuilder()
.cacheControl(cacheControl)
.removeHeader("Pragma")
.build();
}
return chain.proceed(request);
}
};
public static Interceptor provideCacheInterceptor() {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
// re-write response header to force use of cache
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(2, TimeUnit.MINUTES)
.build();
return response.newBuilder()
.header(CACHE_CONTROL, cacheControl.toString())
.removeHeader("Pragma")
.build();
}
};
}
I am using jsonplaceholder.typicode.com/photos that returns:
content-type: application/json; charset=utf-8
date: Sun, 21 Oct 2018 14:26:41 GMT
set-cookie: __cfduid=d9e935012d2f789245b1e2599a41e47511540132001; expires=Mon, 21-Oct-19 14:26:41 GMT; path=/; domain=.typicode.com; HttpOnly
x-powered-by: Express
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
expires: Sun, 21 Oct 2018 18:26:41 GMT
x-content-type-options: nosniff
etag: W/"105970-HCYFejK2YCxztz8++2rHnutkPOQ"
via: 1.1 vegur
cf-cache-status: REVALIDATED
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 46d466910cab3d77-MXP
Cache-Control: public, max-age=60
June 2021 (Retrofit 2.9.0 or OKHTTP 3.14.9) Complete Solution (Update)
Same approach is still working since: Oct. 2018
Oct. 2018 (Retrofit 2.4 or OKHTTP 3.11) Complete Solution
Ok, so Online & Offline caching using OKHTTP or Retrofit has been causing so many problems for many people on stackoverflow and other forums. There are tons of misleading information and non-working code samples all over the internet.
So, today I will explain how you can implement online & offline caching using Retrofit & OKHTTP with clear steps + How to test and know whether you are getting the data from cache or network.
If you are getting a 504 Unsatisfiable Request (only-if-cached) OR an Unable to resolve host "HOST": No address associated with hostnamethen you can use any of the following solutions.
Before you begin, you must always remember to:
Make sure you are using a GET request and not a POST!
Always make sure you add .removeHeader("Pragma") as shown below (This lets you override the server's caching protocol)
Avoid using the HttpLoggingInterceptor while testing, it can cause some confusion in the beginning. Enable it in the end if you want.
ALWAYS ALWAYS ALWAYS delete your app from the device and reinstall it again upon every change in code, if you want to explore using Interceptors. Otherwise changing code while the old cache data is still on the device will cause you lots of confusion and misleading deductions!
The order of adding Interceptors to OKHTTPClient object matters!
N.B: If you want to depend on your server's caching protocol for online and offline caching, then don't read the 2 solutions. Just read this article. All you need is to create a cache object and attache it to OKHTTPClient object.
Solution 1: (Longer, but you have full control)
Step 1: (Create onlineInterceptor)
static Interceptor onlineInterceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response response = chain.proceed(chain.request());
int maxAge = 60; // read from cache for 60 seconds even if there is internet connection
return response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")
.build();
}
};
Step 2: (Create Offline Interceptor) (Only if you want cache access when offline)
static Interceptor offlineInterceptor= new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isInternetAvailable()) {
int maxStale = 60 * 60 * 24 * 30; // Offline cache available for 30 days
request = request.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return chain.proceed(request);
}
};
Step 3: (Create a cache object)
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(context.getCacheDir(), cacheSize);
Step 4: (Add interceptors and cache to an OKHTTPClient object)
OkHttpClient okHttpClient = new OkHttpClient.Builder()
// .addInterceptor(provideHttpLoggingInterceptor()) // For HTTP request & Response data logging
.addInterceptor(OFFLINE_INTERCEPTOR)
.addNetworkInterceptor(ONLINE_INTERCEPTOR)
.cache(cache)
.build();
Step 5:(If you are using Retrofit, add the OKHTTPClient object to it)
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
DONE!
Solution 2: (Just use a library to do all that for you! But deal with the limitations)
Use OkCacheControl library
Step 1 (Create Cache object as shown above)
Step 2 (Create an OKHTTPClient object)
OkHttpClient okHttpClient = OkCacheControl.on(new OkHttpClient.Builder())
.overrideServerCachePolicy(1, MINUTES)
.forceCacheWhenOffline(networkMonitor)
.apply() // return to the OkHttpClient.Builder instance
//.addInterceptor(provideHttpLoggingInterceptor())
.cache(cache)
.build();
Step 3:(Attach the OKHTTPClient object to Retrofit as shown above)
Step 4: (Create a NetworkMonitor Object)
static OkCacheControl.NetworkMonitor networkMonitor=new
OkCacheControl.NetworkMonitor() {
#Override
public boolean isOnline() {
return isInternetAvailable();
}
};
DONE!
Testing:
In order to know whether your device is getting data from the network or from cache, simply add the following code to your onResponse method of Retrofit.
public void onResponse(Call<List<RetroPhoto>> call, Response<List<RetroPhoto>> response) {
if (response.raw().cacheResponse() != null) {
Log.e("Network", "response came from cache");
}
if (response.raw().networkResponse() != null) {
Log.e("Network", "response came from server");
}
}
If the device is using the Network, you will get "response came from server".
If device is using Cache, you will get both of the above responses! For more info about this read this article.
For more info about using OKHTTP interceptors go to this page.
I am new to OkHttpClient and i don't know how to store cache for only 1 week.
So when agent update data, it will update in mobile too after 1 week.
You can use MaxAge and MaxStale parameter of CacheControl
MaxAge
Sets the maximum age of a cached response. If the cache response's age exceeds MaxAge it will not be used and a network request will be made
MaxStale
Accept cached responses that have exceeded their freshness lifetime by up to MaxStale. If unspecified, stale cache responses will not be used
public Interceptor provideCacheInterceptor(final int maxDays) {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(maxDays, TimeUnit.DAYS)
.build();
return response.newBuilder()
.header(Constants.CACHE_CONTROL, cacheControl.toString())
.build();
}
};
}
And later you can add this to your HttpClient
int MaxCacheDays = 7;
httpClient.addNetworkInterceptor(provideCacheInterceptor(MaxCacheDays));
I'm trying to setup an HTTP cache using Retrofit (2.1.0) and OkHttp (3.3.1). I have seen many posts related to this topic, but none of them helped.
I wrote some unit tests to see how the cache works. It works just fine, but once integrated in my app, the magic ends. I will first show you my implementation and then explain some of my investigation.
First, here is my Retrofit instantiation :
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = httpBuilder
.addNetworkInterceptor(INTERCEPTOR_RESPONSE_SET_CACHE)
.addNetworkInterceptor(INTERCEPTOR_REQUEST_ADD_CHECKSUM)
.addInterceptor(loggingInterceptor)
.cache(cacheHttpClient).build();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(BASE_URL)
.build();
Here is the interceptor adding a header to set cache control:
private final Interceptor INTERCEPTOR_RESPONSE_SET_CACHE = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
response = response.newBuilder()
.header("Cache-Control", "max-age=600") //+ Integer.toString(3600 * 5)
.build();
return response;
}
};
The last interceptor adds 2 URL parameters:
private static final Interceptor INTERCEPTOR_REQUEST_ADD_CHECKSUM = new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
HttpUrl url = chain.request().url();
url = url.newBuilder().addQueryParameter("rd", "random1").addQueryParameter("chk","check1").build();
Request request = chain.request().newBuilder().url(url).build();
return chain.proceed(request);
}
};
Finally, the single method of my service :
#Headers("Cache-Control: public, max-stale=500")
#GET("/get_data")
Call<DataResponse> getData(#Query("year") int year, #Query("month") int month, #Query("day") int day);
About my investigation, I setup an interceptor logger (app side, not network) to see what is happening. I can see lines such as "Cache-Control: public, max-stale=500" in my logs. This means (at least to me) that the header should give an opportunity to the OkHttp client to check the cache.
The cache itself seems to be correctly initialised. When I create it, I force the initialisation and log all the urls present in the cache. Here is how it is implemented:
File httpCacheDirectory = new File(getCacheDir(), "responses");
httpCacheDirectory.getParentFile().mkdirs();
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
try {
cache.initialize();
Iterator<String> iterator = cache.urls();
Log.i(TAG, "URLs in cacheHttpClient : ");
while (iterator.hasNext()) {
Log.i(TAG, iterator.next());
}
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "CACHE NOT INIT");
}
When I launch my app with Wifi available, I get the expected responses. Then I kill my app, disable Wifi and relaunch the app. I expect the cache to serve data at this moment. But it fails and I can only see OkHttp printed lines in logs :
HTTP FAILED: java.net.UnknownHostException: Unable to resolve host
"my-domain.com": No address associated with hostname
Last thing, in RFC 2616, one can read :
max-stale : Indicates that the client is willing to accept a response
that has exceeded its expiration time. If max-stale is assigned a
value, then the client is willing to accept a response that has
exceeded its expiration time by no more than the specified number of
seconds. If no value is assigned to max-stale, then the client is
willing to accept a stale response of any age.
When I don't specify an value, it actually works (I get a response even when the Wifi is down). For now this is the only way I found to make it "work". So maybe I just misunderstand the cache-control directive !?
At this point I'm really confused. I really would like to be able to use OkHttp cache system, but somehow I'm missing something.
Thank you for reading all that text !
Use this method to create cached okkhttpclient
private OkHttpClient createCachedClient(final Context context) {
File httpCacheDirectory = new File(context.getCacheDir(), "cache_file");
Cache cache = new Cache(httpCacheDirectory, 20 * 1024 * 1024);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setCache(cache);
okHttpClient.interceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
okHttpClient.networkInterceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
return okHttpClient;
}
private boolean isOnline(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null)
for (int i = 0; i < info.length; i++)
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
return false;
}
Call createCachedClient() method to create OkHttpClient add this client to retrofit
OkHttpClient okHttpClient = createCachedClient(MainActivity.this);
Retrofit retrofit=new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(API)
.addConverterFactory(GsonConverterFactory
.create()).build();
Add this permission to manifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
If internet is available first time it will call the service and cache the request,next time onwards upto 2419200 milliseconds it will use cache to give response.it won't hit server upto 2419200 milliseconds even if device if offline.
I'm trying to cache response via OkHttp and Retrofit. I understand there are several questions similar to mine but none of those are able to address my issue.
Following is my Interceptor responsible for modifying the headers.
private static class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", (UtilityMethods.isNetworkAvailable()) ?
"public, max-age=60" : "public, max-stale=604800")
.build();
}
}
Now, this works perfectly in the first case -
Internet connection is available.
A valid response is received and cached.
Disconnect the device from the internet.
Send the same request as previous one within a minute -> Response is same as last
Next, send the same request after a minute is complete -> No response (UnknownHostException)
This makes the first part ("public, max-age=60") complete.
But, somehow, "public, max-age=60" part does not work at all.
This part should enable okhttp to fetch the a week old stale data when the device is offline but instead, I get UnknownHostException.
I think this is what you are looking for :
.header("Cache-Control", (UtilityMethods.isNetworkAvailable()) ?
"public, max-age=60" : "public, only-if-cached, max-stale=604800")
This adds the only-if-cached directive for when the network is unavailable. This only accepts the response if it is in the cache.
I'm trying to configure cache with Retrofit 1.9.0 and OkHtttp 2.5.0.
Here is how I provide OkHttpClient for my RestAdapter:
#Provides
#Singleton
public OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
okHttpClient.setWriteTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
File cacheDir = new File(context.getCacheDir(), "http");
final Cache cache = new Cache(cacheDir, DISK_CACHE_SIZE_IN_BYTES);
okHttpClient.setCache(cache);
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(request);
Response finalResponse = response.newBuilder()
.header("Cache-Control", String.format("public, max-stale=%d", 604800))
.build();
Log.d("OkHttp", finalResponse.toString());
Log.d("OkHttp Headers", finalResponse.headers().toString());
return finalResponse;
}
});
return okHttpClient;
}
I did not forget to setClient on RestAdapter.Builder. Also made sure, that I'm actually using instance of RestAdapter with this client set.
Even checked if the files are created under "http" folder. They are.
However after I turn of WIFI and reload my screen I end up in OnError callback of Observable endpoint with this message:
retrofit.RetrofitError: failed to connect to /10.40.31.12 (port 8888) after 10000ms: connect failed: ENETUNREACH (Network is unreachable)
DISCLAIMER: I should probably mention that the final Observable is combined from 5 others, with flatMap and zip on the way.
I think I have an answer. Short one is: "Cannot be done if server sends no-cache header in response".
If you want the longer one, details are below.
I've made a sample app comparing 2 backends. Lets call them Backend A, and Backend B. A was giving me troubles so I've decided to check on B.
A returns CacheControl = "no-cache, no-transform, max-age=0"
B returns Cache-Control = „public" response header
I did the same setup for both backends, just different urls.
private void buildApi() {
Gson gson = new GsonBuilder().create();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
File cacheDir = new File(getCacheDir(), "http");
final Cache cache = new Cache(cacheDir, 1000000 * 10);
okHttpClient.setCache(cache);
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Log.d("OkHttp REQUEST", request.toString());
Log.d("OkHttp REQUEST Headers", request.headers().toString());
Response response = chain.proceed(request);
response = response.newBuilder()
.header("Cache-Control", String.format("public, max-age=%d, max-stale=%d", 60, RESPONSE_CACHE_LIFESPAN_IN_SECONDS))
.build();
Log.d("OkHttp RESPONSE", response.toString());
Log.d("OkHttp RESPONSE Headers", response.headers().toString());
return response;
}
});
RestAdapter.Builder builder = new RestAdapter.Builder()
.setConverter(new StringGsonConverter(gson))
.setClient(new OkClient(okHttpClient))
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
if (isNetworkAvailable()) {
request.addHeader("Cache-Control", "public, max-age=" + 60);
} else {
request.addHeader("Cache-Control", "public, only-if-cached, max-stale=" + RESPONSE_CACHE_LIFESPAN_IN_SECONDS);
}
}
});
builder.setEndpoint("http://this.is.under.vpn.so.wont.work.anyway/api");
A_API = builder.build().create(AApi.class);
builder.setEndpoint("http://collector-prod-server.elasticbeanstalk.com/api");
B_API = builder.build().create(BApi.class);
}
Did both calls, then disabled wifi.
Cache worked fine for B, but A thrown 504 Unsatisfiable Request (only-if-cached)
It seems that overwritting headers won't help in that case.
You should rewrite your Request instead of the Response. For reference, see the docs on rewriting requests. Note you, can also use the CacheControl class instead of building your own header if you want. Your interceptor should look something like --
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request cachedRequest = request.newBuilder()
.cacheControl(new CacheControl.Builder()
.maxStale(7, TimeUnit.DAYS)
.build())
.build();
return chain.proceed(cachedRequest);
}
});