Custom retrofit2 annotations - revisited
On the 10/09/2020 I updated my article from 2016, just rewriting it with Kotlin in mind.
Recently I found a simpler way of creating custom Annotations.
In this article I will explain how to create a custom annotation for retrofit.
Why do you need custom annotations?
I guess in most cases you don’t need to use custom annotations, but interceptors. Since retrofit2 is based on okhttp, you can use interceptors. But what does it do? Well, basically every request you call (using retrofit2 or plain okhttp) calls all setup interceptors before executing the requests. The Interceptor is an interface which contains only one method to implement, called (surprise ^^) intercept, which get’s an object of Chain to proceed.
How to use Interceptors?
You can use Chain
to execute
1 | return chain.proceed(chain.request()) // returns Response |
or modify request and execute
1 | val request = chain.request().newBuilder() |
the next upcoming request.
This setup applies for ALL requests that run with the okhttp client in which you set up these interceptors.
Sometimes this is not enough, especially when you have runtime requirements for the modifications of the request. I actually came across this while I wanted to have a configurable annotation for caching some (not all) responses.
So I wrote a library, that provides an annotation for response cache.
Implementing a custom annotations
Defining the annotation
I guess this is the easiest part, just define your annotation as you like it. In my case I needed an annotation that can take some Information.
1 |
|
Linking the request with the annotation
Now we need to read the annotation and pass containing data (in our example above the value: String
) to an Interceptor
in which we can modify using this data. Here comes to the biggest difference towards the previous version of this article.
In order to get information about the request, retrofit tags the request with an Invocation
tag. Using this, we can extract the annotation for the request.
1 | private fun findAnnotation( |
Manipulating the request
Now we can adjust the request/response as we would like to, using the data from our annotation.
1 | class SomeCustomInterceptor : Interceptor { |
When intercepting the request we check if the request was annotated. If so, we modify the request as we need it, if not we just execute it.
Is this it?
Actually, yes. Now we just need to make sure that our Interceptor is applied.
1 | val retrofit = Retrofit.Builder() |
And now we’re able to use our newly created annotation.
Improvements
Problem: Reflection
Compared to the previous article, this version is using reflection to find the annotation that was added to the request. In order to minimize the problem I added some caching to the reflection part, so that it only uses reflection once and not every time the call is done.
1 | private val registration: MutableMap<Int, SomeAnnotation> = mutableMapOf() |
If you want to go completely without reflections, use the other article.
Note: retrofit itself is also using reflections to stub the methods from the api interfaces, it’s also caching them, similar to this solution.
Summary
In this article we discovered how to implement a custom annotation for retrofit2.