Retrofit caching

How does caching work in Retrofit and how can we implement it in our application

For a more complete and in-depth explanation, check out our complete Retrofit course

Download the starter project here

Github repository

The idea behind caching is that responses are valid for a certain amount of time and when we perform the same request multiple times within that timeframe it might make sense to not consume the bandwidth and make the server retrieve the same result, we’re going to use the same result that we received the last time we made that same request.

There are a few caveats here. Caching only works if it is supported by the server. So it’s the server that tells us how long the request should be valid and when we should perform a new request because the data might have been invalidated.

And it kind of makes sense that it’s supported by the server because there are some applications for which caching is useful, for instance regular HTTP requests don’t really change that often. But there are other requests that can change very drastically, for instance if you are watching the stock market for instance you might want to avoid caching altogether so that each request you perform will get you updated results because they can change each second or millisecond.

So caching does make sense in some situations and not others, and it is the server that gives us the timeframe in which caching is valid.

Implementing caching

The way we implement caching in Retrofit is through the Http client. In the same way what we’ve done for logging, we’re going to use the same structure to implement caching. It makes sense because caching is a lower level functionality that should be supported b the lower level library rather than the Retrofit library which simply handles communication back and forth with the server.

So how do we implement this mechanism in our project?

We need to define a cache object that needs a directory and the size of the cache itself.

val cache = Cache(context.cacheDir, cacheSize)

If we have a limited size, all the requests that come in might be stored in that cache until that is filled up and then new requests that come in will push out older requests.

Once we have that, we can use our http client builder that will take a parameter cache.

okhttp3Client.cache(cache)

That is pretty much all you need to do in order to implement caching in your app.

One thing I want to mention here is that in our project we will require some modification. The reason for that is the directory in Android is given to us by the context. So the context gives us the directory, and we will need the context in our ApiService class in order to instantiate the cache.

So let’s have a look and see how we can modify our code in order to have access to the context and create the cache.

Firstly, in the ApiServiceClass, update the call method to receive a context.

fun call(context: Context) = getApi(context).callGet()

Create the getApi method, move the api creation to it, add caching and return the api that can be used b the call method

private var api: ApiCall? = null

private fun getApi(context: Context): ApiCall {

    if (api == null) {

        val okhttp2Client = OkHttpClient.Builder()

        val logging = HttpLoggingInterceptor()

        logging.level = HttpLoggingInterceptor.Level.BODY

        if (BuildConfig.DEBUG) {

            okhttp2Client.addInterceptor(logging)

        }

        val cacheSize = 5 * 1024 * 1024L

        val cache = Cache(context.cacheDir, cacheSize)

        okhttp2Client.cache(cache)

        okhttp2Client.addInterceptor { chain ->

            val request = chain.request()

            val newRequest = request.newBuilder()

                .addHeader(“user-agent”, “Android”)

                .build()

            chain.proceed(newRequest)

        }

        api = Retrofit.Builder()

            .baseUrl(BASE_URL)

            .addConverterFactory(GsonConverterFactory.create())

            .client(okhttp2Client.build())

            .build()

            .create(ApiCall::class.java)

    }

    return api!!

}

In the MainViewModel, make the class extend from AndroidViewModel instead of a simple ViewModel. Note that this is an Android construct that allows us to have access to an application context inside our view model.

class MainViewModel(application: Application): AndroidViewModel(application) {

Inside the fetchData function, update the call method to pass a context parameter

val call = ApiCallService.call(getApplication())

Everything else should stay the same.

If everything works well, you won’t see any error and you won’t see any difference in output. That’s because caching works behind the scenes to provide us with its functionality.

For a more complete and in-depth explanation, check out our complete Retrofit course

Close Bitnami banner
Bitnami