Can Retrofit with OKHttp use cache data when offline Can Retrofit with OKHttp use cache data when offline java java

Can Retrofit with OKHttp use cache data when offline


Edit for Retrofit 2.x:

OkHttp Interceptor is the right way to access cache when offline:

1) Create Interceptor:

private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {    @Override public Response intercept(Chain chain) throws IOException {        Response originalResponse = chain.proceed(chain.request());        if (Utils.isNetworkAvailable(context)) {            int maxAge = 60; // read from cache for 1 minute            return originalResponse.newBuilder()                    .header("Cache-Control", "public, max-age=" + maxAge)                    .build();        } else {            int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale            return originalResponse.newBuilder()                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)                    .build();        }    }

2) Setup client:

OkHttpClient client = new OkHttpClient();client.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);//setup cacheFile httpCacheDirectory = new File(context.getCacheDir(), "responses");int cacheSize = 10 * 1024 * 1024; // 10 MiBCache cache = new Cache(httpCacheDirectory, cacheSize);//add cache to the clientclient.setCache(cache);

3) Add client to retrofit

Retrofit retrofit = new Retrofit.Builder()        .baseUrl(BASE_URL)        .client(client)        .addConverterFactory(GsonConverterFactory.create())        .build();

Also check @kosiara - Bartosz Kosarzycki's answer. You may need to remove some header from the response.


OKHttp 2.0.x (Check the original answer):

Since OKHttp 2.0.x HttpResponseCache is Cache, setResponseCache is setCache. So you should setCache like this:

        File httpCacheDirectory = new File(context.getCacheDir(), "responses");        Cache cache = null;        try {            cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);        } catch (IOException e) {            Log.e("OKHttp", "Could not create http cache", e);        }        OkHttpClient okHttpClient = new OkHttpClient();        if (cache != null) {            okHttpClient.setCache(cache);        }        String hostURL = context.getString(R.string.host_url);        api = new RestAdapter.Builder()                .setEndpoint(hostURL)                .setClient(new OkClient(okHttpClient))                .setRequestInterceptor(/*rest of the answer here */)                .build()                .create(MyApi.class);

Original Answer:

It turns out that server response must have Cache-Control: public to make OkClient to read from cache.

Also If you want to request from network when available, you should add Cache-Control: max-age=0 request header. This answer shows how to do it parameterized. This is how I used it:

RestAdapter.Builder builder= new RestAdapter.Builder()   .setRequestInterceptor(new RequestInterceptor() {        @Override        public void intercept(RequestFacade request) {            request.addHeader("Accept", "application/json;versions=1");            if (MyApplicationUtils.isNetworkAvailable(context)) {                int maxAge = 60; // read from cache for 1 minute                request.addHeader("Cache-Control", "public, max-age=" + maxAge);            } else {                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale                request.addHeader("Cache-Control",                     "public, only-if-cached, max-stale=" + maxStale);            }        }});


All of the anwsers above did not work for me. I tried to implement offline cache in retrofit 2.0.0-beta2. I added an interceptor using okHttpClient.networkInterceptors() method but received java.net.UnknownHostException when I tried to use the cache offline. It turned out that I had to add okHttpClient.interceptors() as well.

The problem was that cache wasn't written to flash storage because the server returned Pragma:no-cache which prevents OkHttp from storing the response. Offline cache didn't work even after modifying request header values. After some trial-and-error I got the cache to work without modifying the backend side by removing pragma from reponse instead of the request - response.newBuilder().removeHeader("Pragma");

Retrofit: 2.0.0-beta2; OkHttp: 2.5.0

OkHttpClient okHttpClient = createCachedClient(context);Retrofit retrofit = new Retrofit.Builder()        .client(okHttpClient)        .baseUrl(API_URL)        .addConverterFactory(GsonConverterFactory.create())        .build();service = retrofit.create(RestDataResource.class);

...

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 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();                    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 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();                    Response response = chain.proceed(request);                    return response.newBuilder()                        .removeHeader("Pragma")                        .removeHeader("Cache-Control")                        .header("Cache-Control", cacheHeaderValue)                        .build();                }            }    );    return okHttpClient;}

...

public interface RestDataResource {    @GET("rest-data")     Call<List<RestItem>> getRestData();}


My solution:

private BackendService() {    httpCacheDirectory = new File(context.getCacheDir(),  "responses");    int cacheSize = 10 * 1024 * 1024; // 10 MiB    Cache cache = new Cache(httpCacheDirectory, cacheSize);    httpClient = new OkHttpClient.Builder()            .addNetworkInterceptor(REWRITE_RESPONSE_INTERCEPTOR)            .addInterceptor(OFFLINE_INTERCEPTOR)            .cache(cache)            .build();    Retrofit retrofit = new Retrofit.Builder()            .baseUrl("https://api.backend.com")            .client(httpClient)            .addConverterFactory(GsonConverterFactory.create())            .build();    backendApi = retrofit.create(BackendApi.class);}private static final Interceptor REWRITE_RESPONSE_INTERCEPTOR = chain -> {    Response originalResponse = chain.proceed(chain.request());    String cacheControl = originalResponse.header("Cache-Control");    if (cacheControl == null || cacheControl.contains("no-store") || cacheControl.contains("no-cache") ||            cacheControl.contains("must-revalidate") || cacheControl.contains("max-age=0")) {        return originalResponse.newBuilder()                .header("Cache-Control", "public, max-age=" + 10)                .build();    } else {        return originalResponse;    }};private static final Interceptor OFFLINE_INTERCEPTOR = chain -> {    Request request = chain.request();    if (!isOnline()) {        Log.d(TAG, "rewriting request");        int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale        request = request.newBuilder()                .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)                .build();    }    return chain.proceed(request);};public static boolean isOnline() {    ConnectivityManager cm = (ConnectivityManager) MyApplication.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);    NetworkInfo netInfo = cm.getActiveNetworkInfo();    return netInfo != null && netInfo.isConnectedOrConnecting();}