Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit Adding tag to the original request object

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.

like image 654
Rahul Chaurasia Avatar asked Feb 06 '17 11:02

Rahul Chaurasia


2 Answers

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")
like image 79
Vlad Avatar answered Nov 01 '22 21:11

Vlad


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";
like image 27
maxoumime Avatar answered Nov 01 '22 22:11

maxoumime