2
votes

I have a bunch of retrofit observables that require authorization token in the header for every request. Token has an expiry time of an hour.

@GET("objects/abc")
    Observable<List<Object>> apiCustomView(@Query("isMobileViews") boolean isMobileViews);

This is my Retrofit service class where authorization token is provided for each request.

class Factory {
        public static RetrofitService create() {
            Interceptor interceptor = chain -> {
                Request newRequest = chain.request().newBuilder().addHeader("Authorization",
                        "Bearer "+ Util.getSharedPref("token")).build();
                return chain.proceed(newRequest);
            };

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.interceptors().add(interceptor);
            OkHttpClient client = builder.build();

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("http://url.com/api/")
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
            return retrofit.create(RetrofitService.class);
        }
    }

Here is consuming the service to get the data -

retrofitService.apiCustomView(isMobileViews)
   .observeOn(AndroidSchedulers.mainThread())
   .subscribeOn(application.defaultSubscribeScheduler())
   .subscribe(new Subscriber < List < Object >> () {
    @Override
    public void onCompleted() {
     if (!mObjects.isEmpty()) {
      // Add to database and present data
     }
    }

    @Override
    public void onError(Throwable error) {
     // Present Data from Database
    }

    @Override
    public void onNext(List < Object > objectList) {
    mObjects = objectList;
    }
 });

I want to modify the request in such a way that whenever I receive authorization failed error, I will fetch the authorization token and resume the same request. On any other type of error, I want to show saved data.

2

2 Answers

2
votes

You can access the network response from Retrofit by changing the return type of your Observable:

@GET("objects/abc")
Observable<Response<List<Object>>> apiCustomView(@Query("isMobileViews") boolean isMobileViews);

Then you can check the error type in a flatmap:

retrofitService.apiCustomView(isMobileViews)
   .flatMap(new Func1<Response<List<Object>>, Observable<List<Object>() {
        @Override
        public Observable<List<Object>> call(Response<List<Object>> response) {

            if (response.isSuccess()) {
                return Observable.just(response.body());
            }
            else if (response.code() == 403) {
                // get new auth token and try again
                return getNewAuthKeyAndTryAgain();
            }
            else {
                return Observable.error(/* create an exception*/)
            }
        }
   })
   .subscribeOn(application.defaultSubscribeScheduler())
   .observeOn(AndroidSchedulers.mainThread())
   .subscribe(...);

To get the new auth token and run the request again I would create a function which does this:

private Observable<List<Object>> getNewAuthKeyAndTryAgain() {

    return retrofitService
                .getNewKey()
                .flatMap(new Func1<String, Observable<Response<List<Object>>>>() {
                    @Override
                    public Observable<Response<List<Object>>> call(String s) {

                        // save key where you need to
                        return retrofitService.apiCustomView(isMobileViews);
                    }
                })
                .flatMap(new Func1<Response<List<Object>>, Observable<List<Object>>>() {
                    @Override
                    public Observable<List<Object>> call(Response<List<Object> response) {
                        return response.body();
                    }
                });
}
2
votes

Not enough reputation to comment so adding as an answer.

I had the similar case where During an api response the Accestoken(Authentication token) may expire and have to get a new Accesstoken from the Server.I had many api calls so came up with this Way. My requirement was

  1. GETdata from server using accesstoken
  2. if(failed)>>call refresh access token api then call the previous api
  3. if(refreshing token failed) get a new one from server and then Call the api

Below is the Code for the Purpose.Its working

    public class RxFunctions {
    public static <T> Observable.Transformer<T, T> applySchedulers() {
        return new Observable.Transformer<T, T>() {
            @Override
            public Observable<T> call(Observable<T> observable) {
                return observable.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
    public static Observable<String> getRefreshtokenObservable(final Context context){
        //Trying to refresh the Accesstoken here ,Notice:Not trying to get a new Access token using Device Id
        String refresh_accesstoken = DAUtilities.getDataFromSp(context, null, Key.REFRESH_ACCESS_TOKEN);
        Log.e("refreh",refresh_accesstoken);
        if(refresh_accesstoken==null)return getApiSecretObservable(context);
        else return  AccessToken.refreshAccessTokenObservable(context)
                .switchMap(new Func1<AccessTokenResponse, Observable<String>>() {
                    @Override
                    public Observable<String> call(AccessTokenResponse accessTokenResponse) {
                        int status = accessTokenResponse.getCode();
                        if (status == 200) {
                            //Refreshing was successful so not calling for new Accesstoken
                            AccessToken.saveToken(accessTokenResponse.getData(), context);
                            return booleanObservable(accessTokenResponse.getData().getAccess_token());
                        } else return getApiSecretObservable(context);
                        //Refreshing was unsuccessfull we have to get a new AccessToken using device id which takes two Api calls Phewwwww
                    }
     });
    }

    public static Observable<String> getApiSecretObservable(final Context context){
        return AccessToken.getApiScretObservable(context).
                flatMap(new Func1<ApiSecretResponse, Observable<String>>() {
                    @Override
                    public Observable<String> call(ApiSecretResponse apiSecretResponse) {
                        int status = apiSecretResponse.code;
                        if (status == 200) {
                            String apiSecret = apiSecretResponse.data;
                            DAUtilities.saveDataToSp(context.getApplicationContext(), apiSecret, Key.API_SECRET);
                            return getAccesstokenObservable(context, apiSecretResponse.data);
                            //Got api secret using device id and now have to get the Accesstoken
                        } else return booleanObservable(apiSecretResponse.message.message);
                        //There was an error gettting the api secret
                    }
                });
    }

    public static Observable<String>getAccesstokenObservable(final Context context, String data)
    {
        return AccessToken.getAccessTokenObservable(context,data).doOnNext(new Action1<AccessTokenResponse>() {
            @Override
            public void call(AccessTokenResponse accessTokenResponse) {
                int status = accessTokenResponse.getCode();
                if (status == 200) {
                    String accessToken = accessTokenResponse.getData().getAccess_token();
                    String refreshToken = accessTokenResponse.getData().getRefresh_token();
                    String issued_at = accessTokenResponse.getData().getIssued_at();
                    String expiry = accessTokenResponse.getData().getExpiry();
                    String signCreated = accessToken + "_" + refreshToken + "_" + expiry + "_" + issued_at;

                    AccessToken.saveToken(accessTokenResponse.getData(),context);
                }//got the accesstoken saving it to the Shared preference and returning a boolean observable
            }
        }).delay(1000, TimeUnit.MILLISECONDS).flatMap(new Func1<AccessTokenResponse, Observable<String>>() {
        @Override
        public Observable<String> call(AccessTokenResponse accessTokenResponse) {
            return booleanObservable(accessTokenResponse.getData().getAccess_token());
        }
       });
    }

    public static Observable<String> booleanObservable(String accesstoken)
    {
        return Observable.just(accesstoken).compose(RxFunctions.<String>applySchedulers());
    }



}

apicall in another class

@Override
public void getItems(String accesstoken, String userId, int pagenumber, final DatafromServer datafromServer, Context context) {
            getObservable(accesstoken, userId, pagenumber).
                    subscribe(getresponseObserver(userId, pagenumber, datafromServer, context)); }

private Observable<DaResponse> getObservable(String accesstoken,String userId,int pagenumber)
    {        return RxRestSingleton.getInstance().getFavourites(userId,accesstoken,pagenumber,perpage).compose(RxFunctions.<DaResponse>applySchedulers()); }

    private Observer<DaResponse>getresponseObserver(final String userId,final int pagenumber,final DatafromServer datafromServer,final Context context)
    {
        return new Observer<DaResponse>() {
            @Override
            public void onCompleted() {
            }
            @Override
            public void onError(Throwable e) {
                setError("here");
                e.printStackTrace();
            }
            @Override
            public void onNext(DaResponse daResponse) {
                if(daResponse.getCode()==200 && !triedtorefresh)//This means that the Accesstoken is invalid and out of date
                {
                    triedtorefresh=true;
                    RxFunctions.getRefreshtokenObservable(context).compose(RxFunctions.<String>applySchedulers())
                            .switchMap(new Func1<String, Observable<DaResponse>>() {
                                @Override
                                public Observable<DaResponse> call(String newAccesstoken) {
                                    return getObservable(newAccesstoken,userId,pagenumber);
                                }
                            }).subscribe(getresponseObserver(userId, pagenumber, datafromServer, context));
                }
                else {
                    favourites = daResponse.getData().getListings();
                    datafromServer.onSetData(favourites);
                }
            }
        };
    }

Please note that the Error is not the retrofit error.ITs the error in the Api cases.Hope this works,Happy coding