4

I use this to config my retrofit:

 RestAdapter restAdapter = new RestAdapter.Builder()

            //add headers to requests
            .setRequestInterceptor(getAuthenticatedRequestInterceptor())
            .setEndpoint(BASE_URL)
            .setConverter(new GsonConverter(getGson()))
            .build();

and The getAuthenticatedRequestInterceptor() method adds headers to request:

public AccountRequestInterceptor getAuthenticatedRequestInterceptor() {
    AccountRequestInterceptor interceptor = new AccountRequestInterceptor();
    Map<String, String> headers = new HashMap<>();
     String accessToken = null;
    try {
        accessToken = TokenProvider.getInstance(mContext).getToken();
    } catch (InterruptedException e) {

    }
    headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
    interceptor.setHeader(headers);
    return interceptor;
}

getToken() method is:

private synchronized string getToken() throws InterruptedException {
    if (!isRefreshing()) {
        //This is very important to call notify() on the same object that we call wait();
        final TokenProvider myInstance = this;
        setRefreshing(true);

        MyApplication.getRestClient().getAccountService().getRefreshedToken(mLoginData.getRefreshToken())
                .subscribe(new Observer<LoginResponse>() {
                    @Override
                    public void onCompleted() {
                        synchronized (myInstance) {
                            setRefreshing(false);
                            myInstance.notifyAll();
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        synchronized (myInstance) {
                            setRefreshing(false);
                            myInstance.notifyAll();
                        }
                    }

                    @Override
                    public void onNext(LoginResponse loginResponse) {
                        synchronized (myInstance) {
                            mLoginData = loginResponse;
                            mAccountProvider.saveLoginData(loginResponse);
                            myInstance.notifyAll();
                        }
                    }
                });
    }
    this.wait();
    return mLoginData.getToken();
}

The TokenProvider.getInstance(mContext).getToken() has a wait() on main thread to get the response from an async method and i know that is a bad thing to do but i need this here to wait for the response to take the token from it and then return the token.how can i do this in a separate thread to avoid waiting on the main thread?

Note:

1 - that this is called before any request with retrofit.

2 - I read this and i know i can refresh token after a fail request, but for business reasons i want to avoid having an invalid token.

3 - I call MyApplication.getRestClient().getAccountService().login(loginRequest,callback...‌​) in my Activity and before adding token everything happened in background thread. so I want to use my token and do not block the main thread.

UPDATE: I added the following Interceptor to my new OkHttp:

public class RequestTokenInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        Request newRequest;
        try {
            Log.d("addHeader", "Before");
            String token = TokenProvider.getInstance(mContext).getToken();
            if (token != null) {
                newRequest = request.newBuilder()
                        .addHeader("Bearer", token)
                        .build();
            } else {
                // I want to cancel the request or raise an exception to catch it in onError method
                // of retrofit callback.
            }
        } catch (InterruptedException e) {
            Log.d("addHeader", "Error");
            e.printStackTrace();
            return chain.proceed(request);
        }
        Log.d("addHeader", "after");
        return chain.proceed(newRequest);
    }
}

Now how can i cancel the request or raise an exception to catch it in onError method of retrofit callback, if token is null?

Community
  • 1
  • 1
Morteza Rastgoo
  • 6,772
  • 7
  • 40
  • 61
  • 2
    Don't send your requests on the main thread? – user253751 Aug 24 '15 at 06:30
  • I use MyApplication.getRestClient().getAccountService().login(loginRequest,callback...) in my activities and before adding token everything happened in background thread. – Morteza Rastgoo Aug 24 '15 at 06:32
  • What @immibis said, also http://stackoverflow.com/questions/22490057/android-okhttp-with-basic-authentication – yiati Aug 24 '15 at 18:04

3 Answers3

2

It's a little bit strange issue but let me try to help you. :)

As you know you can refresh token after a failed request with retrofit using response interceptor.

Let's try to use interceptor before request.

public class RequestTokenInterceptor implements Interceptor {
   @Override
   public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      // Here where we'll try to refresh token.
      // with an retrofit call
      // After we succeed we'll proceed our request
      Response response = chain.proceed(request);
      return response;
   }
}

And when you're creating your api create a new HttpClient:

OkHttpClient client = new OkHttpClient();
client.interceptors().add(new RequestTokenInterceptor());

And add your http client to your adapter like below:

.setClient(new OkClient(client))

If this works, before every request you'll try to refresh token first and then will proceed your api request. So in ui there'll be no difference with your normal api calls.

Edit:

I'm editing my answer too. If you want to return an error in else case if token null, in else case you can create your custom response:

private Response(Builder builder) {
    this.request = builder.request;
    this.protocol = builder.protocol;
    this.code = builder.code;
    this.message = builder.message;
    this.handshake = builder.handshake;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.networkResponse = builder.networkResponse;
    this.cacheResponse = builder.cacheResponse;
    this.priorResponse = builder.priorResponse;
  }

or simply you can return a null response. if you build your custom response and set your code not to 200 such as 401 or 400+ you'll receive that response in Retrofit's callbacks failure method. Than you can do what ever you want.

If you return null you'll get a RuntimeException i think and still you can catch response in your callback's failure method.

After you create your own response in else you can create your custom callback and catch your null response and transform your custom error how ever you want like below:

public abstract class DefaultRequestCallback<T> implements Callback<T> {

    public abstract void failure(YourCustomException ex);

    public abstract void success(T responseBean);

    @Override
    public void success(T baseResponseBean, Response response) {
        if (response == null) {
            // Here we catch null response and transform it to our custom     Exception
            failure(new YourCustomException());
        }
        } else {
            success(baseResponseBean);
        }
    }

    @Override
    public void failure(RetrofitError error) {
        // Here's your failure method.
        // Also you can transform default retrofit errors to your customerrors
        YourCustomException ex = new YourCustomException();
        failure(ex);
    }
}

This can help you i think.

Edit 2:

You can build a new Response like below. There's a builder pattern in Retrofit's Response class. You can check it from there.

Response response = new Response.Builder().setCode(401).setMessage("Error Message").build();
savepopulation
  • 11,736
  • 4
  • 55
  • 80
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/87788/discussion-between-morteza-rastgoo-and-savepopulation). – Morteza Rastgoo Aug 24 '15 at 13:30
1

You could make all long actions in AsyncTask doInBackground method, while in onPre- and onPostExecute you could show/hide some progress bars when user is waiting

Yaroslav Shulyak
  • 151
  • 1
  • 12
  • Avoid AsyncTask like the plague. Instead use, for example, IntentServices. Here's an example why AsyncTask is bad: http://simonvt.net/2014/04/17/asynctask-is-bad-and-you-should-feel-bad/ – dazito Aug 24 '15 at 07:14
1

Ok, I think if you are calling your getAuthenticatedRequestInterceptor() on the main thread and which in turns call getInstance(),in which i feel you would be creating an object of Type TokenProvider hence when you create this object in the main thread your object.wait() runs on main thread hence to run this on a background thread probably modify your getAuthenticatedRequestInterceptor() method to execute the following lines in a new thread.

try {
    accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {

}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;

but this will have problems for notifying your RestAdapter as the main thread will proceed executing, hence i would suggest you call getAuthenticatedRequestInterceptor() method first in a new thread and then notify your main thread to build your RestAdapter.This will free your main thread but with the strategy you are employing you will have to wait until you receive the token to make any calls.

harshitpthk
  • 4,058
  • 2
  • 24
  • 32
  • I don't want to do this because i will have to change the way i call every request i made so far in my app. – Morteza Rastgoo Aug 24 '15 at 13:18
  • this should only change your creation of your RestAdapter. nonetheless more importantly i was trying to explain why your object was waiting on the main thread. – harshitpthk Aug 24 '15 at 14:09