Annotation-Based Offline Caching in Retrofit

Ikhiloya Imokhai
The Startup
Published in
4 min readOct 29, 2020

An annotation is a form of syntactic metadata that provides data about a program but is not part of the program itself. Annotations have no direct effect on the operation of the code they are added to.

In Java, annotations can also be embedded in and read from Java class files generated by the Java compiler which allows annotations to be retained by the Java virtual machine at run-time and read via reflection.

Annotations can be used for the following:

  • Detect errors or suppress warnings e.g @Override and@SuppressWarnings.
  • Compile-time and deployment-time processing e.g code generation tools.
  • Runtime processing.

This article shows how to leverage custom annotations to mark specific web service requests for caching in Retrofit. This is because we usually have a single Retrofit instance to make web service requests to the server. Therefore, it is necessary to devise a means of identifying requests that should be cached.

Use Case

Consider an app that displays a list of payment types (Cash, Bank Transfer, Bitcoin, Card, e.t.c.) which is fetched from a server.

Since the list of payment types rarely change, it makes sense to cache this request to reduce the number of backend calls for this resource. This and such-like requests would qualify for caching.

The Challenge

Having identified requests that qualify for caching, how do we ensure that only such-like requests are retrieved from the cache when there is no internet connection?

That is, how do we “mark” such requests as Cacheable.

The Proposed Solution

To solve this problem, the following are needed:

  1. A cache to store and retrieve the resource.
  2. A network interceptor to cache requests when the network is available.
  3. A custom annotation to mark requests as Cacheable.
  4. An offline interceptor to cache requests when the network is unavailable.
  5. An OKHttp client and a Retrofit instance.

Step 1: Create a Cache

We simply create a cache of size 5MB to store and retrieve the server’s resources.

Please see the reference section for resources on caching in android.

Step 2: Create a Network Interceptor

This interceptor is called when there is a network connection and helps to fetch cached resources without making a network call to the server.

The max-age is set to 5 seconds which specifies the maximum age of a cached response. If the cached response’s age exceeds maxAge, it will not be used and a network request will be made.

To learn more about interceptors please see the links below:

Step 3: Create Custom Annotation

In this step, create a custom runtime annotation with the target set to Function. This annotation would be added to specific requests which would ensure that they are cached. The annotation is added to the Invocation class and made available at runtime.

@Retention(AnnotationRetention.RUNTIME) ensures that the annotation is available at runtime.

@Target(AnnotationTarget.FUNCTION) ensures that the annotation is applied to methods or functions such as the web service request abstract method.

@MustBeDocumented is a meta-annotation that ensures the annotation is included in the generated documentation for the element to which the annotation is applied.

Retrofit automatically adds an invocation to each OkHttp request as a tag. You can retrieve the invocation in an OkHttp interceptor for metrics and monitoring.

Step 4: Create an Offline Interceptor

This interceptor is called when there is no network connection. It intercepts the request and fetches the cached resource without making a call to the server.

In this interceptor, we check if the particular request is marked for caching. That is, we check if the request contains the Cacheable annotation. If the request contains the annotation, the stale cached response is retrieved otherwise it is not.

The first step is to retrieve the Invocation object from the request by tag. This is because Retrofit automatically adds an invocation to each OkHttp request as a tag.

Then, we can get the Cacheable annotation from the invocation instance.

Lastly, we check if the annotation is of type Cacheable before caching the request.

The full offline interceptor code is shown below:

Responses can be retrieved when the freshness lifetime has not exceeded the maxStale duration (7 days in this case). Once the maxStale duration is exceeded, responses can no longer be retrieved.

Step 5: Create OKHttp client and a Retrofit instance

The final step is to create the OKHttp client and retrofit that would contain the interceptors and caching mechanism.

With this, we can comfortably mark our request with the Cacheable annotation.

Unit Test

To ensure that the above implementation works as expected, the unit test below helps to check if the Cacheable annotation is injected for specific requests.

Alternative Solution

Another solution to this challenge is to add a custom header to the request and then check if the added header is present in the interceptor.

The offline interceptor would be:

Conclusion

In this article, we have seen how to use custom annotations to mark specific requests that require offline caching.

The concept presented in this article has been around for a while and it can be applied to different scenarios. Do well to explore other applications.

You can find the source code in the repository below.

Thanks for reading and feel free to leave your comments and suggestions.

--

--

Ikhiloya Imokhai
The Startup

Software Developer | imokhaiikhiloya@gmail.com | https://github.com/Ikhiloya | Reach out to me for Android/Java/Technical writing projects.