1

I have an api at http:/some.api and a GET endpoint data which requires authentication through bearer token received through POST auth endpoint. The given security token is expired every 1 hour so I need to make a POST http:/some.api/auth request in case I receive 401 on GET http:/some.api/data, renew access token and make the same call to data with the new access token without my client knowing anything about it. Current examples provide the Call type which I can enqueue (call) from the UI thread without it stopping, something analogous to .net's async/await feature. Now, if I were using .net, I would wrap up the the whole logic and it would look like this:

async Task<DataModel> GetDataAsync()
{
    try
    {
        return await GetDataInternalAsync();
    }
    catch(InvalidTokenException)
    {
        await ReAuthenticateAsync();
        return await GetDataInternalAsync();
    }
}

and call await GetDataAsync() from the UI thread without worries. However, current examples only show how to make a call and handle results "on spot" from the calling method, for example:

Call<DataModel> call = apiService.getData();  
call.enqueue(new Callback<DataModel>() {  
    @Override
    public void onResponse(Call<DataModel> call, Response<DataModel> response) {
        // todo deal with returned data. what if it is 401 and i need to make the same request after making the request to auth?
    }

    public void onFailure(Call<DataModel> call, Throwable t) {
        // todo deal with the failed network request
    }
});

What would be the way to organize calls architecture for my needs using Retrofit?

Jawad Malik
  • 608
  • 5
  • 21
nicks
  • 2,161
  • 8
  • 49
  • 101
  • If you use kotlin and coroutines and coroutine adapter for retrofit you can just about copy paste that C# snippet and it will work the same way – Tim Oct 02 '19 at 12:24
  • no, i'm using Java – nicks Oct 02 '19 at 12:24
  • 3
    You may want to take a look at RxJava adapter for Retrofit to avoid callback hell. – shinwan Oct 02 '19 at 12:27
  • checkout this https://stackoverflow.com/questions/36785090/chaining-requests-in-retrofit-rxjava – Ankit Oct 02 '19 at 12:36
  • Well, this will be a detailed conversation but I would like to make it short, if you need explanation leave a comment then. You can use livedata with observer from the android architecture components which will help you to check the current response and then depending upon your observed data/response you can decide your following api call or not. Easy process available for solving your problem. – A S M Sayem Oct 02 '19 at 13:52
  • @nicks did you manage to solve it – dglozano Oct 04 '19 at 07:29

2 Answers2

2

Instead of using plain Retrofit Callbacks, I would strongly recommend using a RxJava adapter. Then, you can return Observables from your Retrofit Client, which makes your life way easier when it comes to chaining different calls (using flatMap operator for example).

For handling the token authentication logic that you mention, I would do it using a custom OkHttp3 Authenticator. That way, you can place all the authentication logic (that will probably be used in almost all your API calls) in one single place and OkHttp will call the logic defined to renew your tokens every time it gets a 401 response.

public class CustomOkHttpAuthenticator implements Authenticator {

    ...

    @Override
    public Request authenticate(@NonNull Route route, @NonNull okhttp3.Response response) throws IOException {
        if (response.code() == 401) {
          // Call your refresh token endpoint, wait for the new token, and build
          // again the original request using the new token you received.
        }
        return null;
    }`

    ...
}

Finally, when creating your OkHttp client, that Retrofit will use to perform the HTTP calls, make sure to add your custom Authenticator

OkHttpClient.Builder client = new OkHttpClient.Builder();
        client.connectTimeout(10, TimeUnit.SECONDS);
        client.readTimeout(15, TimeUnit.SECONDS);
        client.writeTimeout(15, TimeUnit.SECONDS);
        ...
        client.authenticator(new CustomOkHttpAuthenticator());
        OkHttpClient okHttpClient = client.build();
        okHttpClientHolder.setOkHttpClient(okHttpClient);
        ...

You can check a complete code example in this repository.

dglozano
  • 6,369
  • 2
  • 19
  • 38
0

Try this:

private static final int MAX_TRIES = 3;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getDataAsync(0);
}

private void getDataAsync(final int currentRetryCount) {
    Call<DataModel> call = apiService.getData();
    call.enqueue(new Callback<DataModel>() {
        @Override
        public void onResponse(@NonNull Call<DataModel> call, @NonNull Response<DataModel> response) {
            if(currentRetryCount<MAX_TRIES && response.code()== HttpURLConnection.HTTP_UNAUTHORIZED){ //401
                authRequest(currentRetryCount+1);
            }
        }

        public void onFailure(@NonNull Call<DataModel> call, @NonNull Throwable t) {
            // todo deal with the failed network request
        }
    });
}

private void authRequest(final int currentRetryCount){
    Call<AuthResponse> call = apiService.authReq();
    call.enqueue(new Callback<AuthResponse>() {
        @Override
        public void onResponse(@NonNull Call<AuthResponse> call, @NonNull Response<AuthResponse> response) {
            getDataAsync(currentRetryCount);
        }

        @Override
        public void onFailure(@NonNull Call<AuthResponse> call, @NonNull Throwable t) {
            // todo deal with the failed network request
        }
    });
}

i have added retryCount to prevent infinite requests, cz chaining request like this can act as recursion as well.

Harsh Jatinder
  • 833
  • 9
  • 15