I'm trying to solve a problem where I'll be making a couple of asynchronous calls and based on the original request, I'm performing a task. To solve this issue, I'm trying to add a TAG to each request and then on successful response, I can get the tag and take action based on the tag. Here, I'm using TAG only to identify the original request.
Problem
Before calling the enqueue method, I'm setting the tag to the original request. But when I get the response in the successful callback, I'm getting different tag that I didn't set. Somehow the request object itself is coming as the tag object there. I'm not sure, how???
Please check the code below-
GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
final Call<List<Contributor>> call = gitHubService.repoContributors("square", "retrofit");
// Set the string tag to the original request object.
call.request().newBuilder().tag("hello").build();
call.enqueue(new Callback<List<Contributor>>() {
@Override
public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
Log.d("tag", response.raw().request().tag().toString());
// I'm getting Request{method=GET, url=https://api.github.com/repos/square/retrofit/contributors, tag=null} as the value of the tag. WHY????
final TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(response.body().toString());
}
@Override
public void onFailure(Call<List<Contributor>> call, Throwable t) {
final TextView textView = (TextView) findViewById(R.id.textView);
textView.setText("Something went wrong: " + t.getMessage());
}
});
Can somebody point out that what exactly I'm doing wrong here. Any help would be appreciated.
For me this code is working
val CLIENT: OkHttpClient = OkHttpClient.Builder().apply {
addInterceptor(TagInterceptor())
}.build()
val SERVER_API: ServerApi = Retrofit.Builder()
.client(CLIENT)
.baseUrl(BASE_URL)
.build()
.create(ServerApi::class.java)
interface ServerApi {
@GET("api/notifications")
@Tag("notifications")
suspend fun getNotifications(): ResponseBody
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Tag(val value: String)
internal class TagInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val builder = request.newBuilder()
request.tag(Invocation::class.java)?.let {
it.method().getAnnotation(Tag::class.java)?.let { tag ->
builder.tag(tag.value)
}
}
return chain.proceed(builder.build())
}
}
Then cancel by tag
fun OkHttpClient.cancelAll(tag: String) {
for (call in dispatcher().queuedCalls()) {
if (tag == call.request().tag()) {
call.cancel()
}
}
for (call in dispatcher().runningCalls()) {
if (tag == call.request().tag()) {
call.cancel()
}
}
}
CLIENT.cancelAll("notifications")
This solution is clearly a hack, but it works.
Let's say you create your Retrofit service like this :
public <S> S createService(Class<S> serviceClass) {
// Could be a simple "new"
Retrofit.Builder retrofitBuilder = getRetrofitBuilder(baseUrl);
// Could be a simple "new"
OkHttpClient.Builder httpClientBuilder = getOkHttpClientBuilder();
// Build your OkHttp client
OkHttpClient httpClient = httpClientBuilder.build();
Retrofit retrofit = retrofitBuilder.client(httpClient).build();
return retrofit.create(serviceClass);
}
You will need to add a new CallFactory
to your Retrofit instance, so it adds a tag every-time. Since the tag will be read-only, we will use an array of Object containing only one element, which you will be able to change later on.
Retrofit retrofit = retrofitBuilder.client(httpClient).callFactory(new Call.Factory() {
@Override
public Call newCall(Request request) {
request = request.newBuilder().tag(new Object[]{null}).build();
Call call = httpClient.newCall(request);
// We set the element to the call, to (at least) keep some consistency
// If you want to only have Strings, create a String array and put the default value to null;
((Object[])request.tag())[0] = call;
return call;
}
}).build();
Now, after creating your call, you will be able to change the contents of your tag:
((Object[])call.request().tag())[0] = "hello";
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With