Reducing your networking footprint with OkHttp, Etags and If-Modified-Since

You have many options to reduce networking usage of your Android app. Low-hanging fruit is the use of If-Modified-Since or Etags headers. It’s already included in OkHttp3, you will just have to enable it.

Imagine your app’s friends list being updated on a website, how do you get the changes to your app, without fetching the list of friends on every app start? You could implement custom logic, or you could enable caching.

To fast forward to the client implementation, scroll to Picking the fruit: Client implementation.

If-Modified-Since and Last-Modified

Using a If-Modified-Since and Last-Modified header combination will enable your server to return a 304 NOT MODIFIED status if there have been no changes after the client’s last request. Because this response does not contain a content body, it is just a few bytes in size.

How it works: The Last-Modified header is set by the server on its response, it is the last-edit-date of the resource that the client is requesting. Let’s say the client requests a list of articles, the Last-Modified header on the response will be the last-edit-date of the most recently edited article in the list.

The Last-Modified response header from the server is used by the client on the next equivalent call as an If-Modified-Since request header. The server checks if any of the requested items changed after the If-Modified-Since date. If not, it will return a 304 NOT MODIFIED status. If there are changes, it will fetch and return the complete result. Depending on how the backend developer implemented this, it might be faster than using an Etag.

Etag and If-None-Match

An Etag functions in a similar way. Etags are less error-prone to implement but require the server to run a complete query and create a hash every time.

How it works: Your backend developer will, before sending a response, create a hash of the response content and details (e.g. using SHA1) and add this as an Etag header. The client will, on a future equivalent request, send this Etag to the server as an If-None-Match header. The server will, after preparing the response, compare its new response hash with the Etag on the request. If it’s the same then the server will return a 304 NOT MODIFIED status without content. If it does not match, then the server will respond regularly, with the new Etag as a header.

Picking the fruit: Client implementation

If you use Retrofit2, enabling Last-Modified or Etags on client side is straightforward, it is just not well documented. Where you create your Retrofit instance, turn on caching:

private final static int CACHE_SIZE_BYTES = 1024 * 1024 * 2;public static Retrofit getAdapter(Context context, String baseUrl) {    OkHttpClient.Builder builder = new OkHttpClient().newBuilder();    builder.cache(
new Cache(context.getCacheDir(), CACHE_SIZE_BYTES));
OkHttpClient client = builder.build(); Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
retrofitBuilder.baseUrl(baseUrl).client(client);
return retrofitBuilder.build();
}

The Last-Modified headers or Etags will be automatically used depending on the servers responses. In addition you will want to reduce processing/parsing time:

Reduce processing

On a 304 status, Retrofit2 and OkHttp3 will pretend this response was equal to the last response; the cached response is returned. To notice the 304, check the raw response code:

if (response.isSuccessful() &&
response.raw().networkResponse() != null &&
response.raw().networkResponse().code() ==
HttpURLConnection.HTTP_NOT_MODIFIED) {
// not modified, no need to do anything.
return;
}
// parse response here

Getting the normal (not raw) response code would give you the cached status code, probably 200 OK:

response.networkResponse().code() // NO

In some cases you might not need to check on HTTP_NOT_MODIFIED. For example, you might want to re-parse the response when you want to show something directly from the network, even though it has not changed.

Troubleshooting

When your Etag or Last-Modified setup is not functioning:

1. Check your Cache-Control headers

Use Stetho or an OkHttp logging interceptor to check your headers. If it shows something like:

Cache-Control: must-revalidate, no-cache, no-store, private // NO

then the server’s cache configuration is incorrect. Cache must be enabled for OkHttp3 caching to work. A correct header would be:

Cache-Control: private, must-revalidate // YES

Using Stetho or a logging interceptor* will also allow you to see the Last-Modified and Etag headers on all your requests and responses. Double check if they are present and correct.

* depending on the interceptor and its position in the list of interceptors, you might not able to see the If-Modified-Since header in your logs when the header is added after your log statements. If so, checking with Charles Proxy will always show you the correct headers.

2. Using both Etags and Last-Modified

OkHttp3 will check the cache headers in this strict order:

  1. If the last response contained an ETag header, the same ETag value is used in the next request as If-None-Match.
  2. If point 1 is false and the last response contained a Last-Modified header, then this value is used in the next request as If-Modified-Since.

So if you use both, the Etag might block your Last-Modified usage. More info here: What’s under the hood of the OkHttp’s cache?

Minor effort

I have seen developers trying to set and save If-Modified-Since and If-None-Match headers manually. There is no need for this if you use Retrofit2 and/or OkHttp3.

Setting up correct cache configurations on client and server side is all you need to do in order to lower the network footprint of your app.

Android developer and hiking addict

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