Annotation-Based Offline Caching in Retrofit
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:
- A cache to store and retrieve the resource.
- A network interceptor to cache requests when the network is available.
- A custom annotation to mark requests as
Cacheable
. - An offline interceptor to cache requests when the network is unavailable.
- 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.