Retrofit 2 — Activate Response Caching (Etag, Last-Modified)

Internet servers and browsers have developed an advanced caching system to reduce the used bandwidth and make viewing websites much faster. However, when developing mobile apps we started to forget about these things. While image libraries like Picasso or Glide provide caching when loading and displaying images, Retrofit does not utilize a cache by default for its requests.

In this tutorial, you'll learn the basics of caching and how you can enable it for your Android app's Retrofit requests.

Retrofit Series Overview

  1. Callbacks (Coming soon)
  2. Annotations (Coming soon)
  3. Fluent Interface with Builders (Coming soon)

Server-Side Information for Caching

We won't go into the details of caching on the Internet and how most servers and browsers implement it, because it would blow up this tutorial. We'll just give an overview. Feel free to read up on the details at the end of the tutorial.

In short, the server can send additional information as response headers with a resource. Based on these headers the clients can sometimes skip loading the exact same resource over and over again. This saves the user's bandwidth and makes your app faster. Win-win!

There are two major ways how clients (apps or browsers) can skip loading the same resource again.

First, the cached version is declared valid until a certain time in the future. This is done by the Expires and Cache-Control: max-age headers. The latter is the more modern and preferred header. When the client accesses the resource, and has not reached the Expires date or stepped over the max-age yet, it can completely skip any network requests and re-use the cached version. An appropriately set life time for the cached resource is critical here.

The second option are so-called conditional requests. Here the first option of not needing to revalidate the resource was not possible, i.e., the resource went over the maximum age. The client will send the server a request with either the previously accessed resource date (If-Modified-Since) and/or a hash of the content (Etag). If the resource didn't change since the client's last access, the server can respond with a 304 - Not Modified status. The response is only the status code and header information and does not contain the actual resource as response payload. Thus the response from the server would be much smaller than a new request without any cache headers. If the current server version was updated and is now different from the cached version (based on the hash or modification date), the server responds with a regular 200 and the resource as response payload.

Based on the very short explanation above, you should get a feeling on how you can optimize your app's network behavior. Ideally, it should utilize both ways of caching resources and reduce the amount of full responses the server needs to send.

Integrate Response Caching in Retrofit

Let's get practical. How can you implement all of this in Retrofit? Well, first and foremost you will generally need server support for this. If your server or API doesn't support any cache headers, optimizing networking with caches will be much harder. In a future tutorial, we'll look at how to force cache support, but that would be beyond the scope of this introductory tutorial.

No matter if you want to force it, or if your API provides it by default, make sure you only cache things that are not time-sensitive! The context is very significant for caching. If your app shows the live stock market, you might not want to cache the API results for two weeks.

Let's assume your API developers did the heavy lifting for you and set smart max-age and Etags settings. The good news: OkHttp, the network layer of Retrofit, supports all the cache headers by default. The only thing you need to do is to provide a cache (a place to store the responses).

int cacheSize = 10 * 1024 * 1024; // 10 MB  
Cache cache = new Cache(getCacheDir(), cacheSize);

OkHttpClient okHttpClient = new OkHttpClient.Builder()  
        .cache(cache)
        .build();

Retrofit.Builder builder = new Retrofit.Builder()  
        .baseUrl("http://10.0.2.2:3000/")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create());

Retrofit retrofit = builder.build();  

The Retrofit instance above will cache all responses until it reached the 10 MB maximum. After it exceeded the cache disk limit, it'll clean up the oldest entries. OkHttp will automatically apply Etag, Cache-Control, etc logic on every request for you. If the resource stayed the same, it won't be loaded again. Incredible!

That's all you have to do to support response caching with Retrofit. Of course, we'll dive into more details in future tutorials, e.g., how to analyze the cache files. Stay tuned!

Summary

In this tutorial, you've learned what options server and clients have to reduce the necessity to load resources again and again. You've also seen how Retrofit (via OkHttp) supports caching on a high level. In the next tutorial, you'll learn how you can detect if a response came from the server, cache or both. The latter is a partial server response with status code 304. That means the headers are from network, the content itself is from the cache.

Do you have further questions on this topic or about Retrofit in general? Just let us know on Twitter @futurestud_io or leave a comment below.

Enjoy coding & make it rock!

Further Readings on Caching

Explore the Library

Find interesting tutorials and solutions for your problems.

Miscellaneous