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
- Getting Started and Creating an Android Client
- Basics of API Description
- Creating a Sustainable Android Client
- URL Handling, Resolution and Parsing
- How to Change API Base Url at Runtime
- Multiple Server Environments (Develop, Staging, Production) (Coming soon)
- Share OkHttp Client and Converters between Retrofit Instances (Coming soon)
- Upgrade Guide from 1.9
- How to use OkHttp 3 with Retrofit 1
- Synchronous and Asynchronous Requests
- Send Objects in Request Body
- Add Custom Request Header
- Manage Request Headers in OkHttp Interceptor
- Dynamic Request Headers with @HeaderMap
- Multiple Query Parameters of Same Name
- Optional Query Parameters
- Send Data Form-Urlencoded
- Send Data Form-Urlencoded Using FieldMap
- How to Add Query Parameters to Every Request
- Add Multiple Query Parameter With QueryMap
- How to Use Dynamic Urls for Requests
- Constant, Default and Logic Values for POST and PUT Requests
- Cancel Requests
- Reuse and Analyze Requests
- Optional Path Parameters
- How to Send Plain Text Request Body
- Customize Network Timeouts (Coming soon)
- How to Trust Unsafe SSL certificates (Self-signed, Expired) (Coming soon)
- Dynamic Endpoint-Dependent Interceptor Actions (Coming soon)
- Introduction to (Multiple) Converters
- Adding & Customizing the Gson Converter
- Implementing Custom Converters
- How to Integrate XML Converter
- Access Mapped Objects and Raw Response Payload
- Supporting JSON and XML Responses Concurrently (Coming soon)
- Handling of Empty Server Responses with Custom Converter
- Send JSON Requests and Receive XML Responses (or vice versa) (Coming soon)
- Unwrapping Envelope Responses with Custom Converter (Coming soon)
- Wrapping Requests in Envelope with Custom Converter (Coming soon)
- Define a Custom Response Converter
- Simple Error Handling
- Error Handling for Synchronous Requests (Coming soon)
- Catch Server Errors Globally with Response Interceptor
- How to Detect Network and Conversion Errors in onFailure (Coming soon)
- Log Requests and Responses
- Enable Logging for Development Builds Only
- Log Network Traffic with Stetho and Chrome Developer Tools
- Using the Log Level to Debug Requests
- Analyze Network Traffic with Android Studio Profiler (Coming soon)
- Activate Response Caching (Etag, Last-Modified)
- Check Response Origin (Network, Cache, or Both)
- Force Server Cache Support with Response Interceptor
- Support App Offline Mode by Accessing Response Caches
- Analyze Cache Files
- Callbacks (Coming soon)
- Annotations (Coming soon)
- 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
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
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
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!
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!