Search code examples
androidrx-javaretrofit2rx-android

Retrofit RxAndroid make wrapper for api calls


I wanted to create a wrapper for api calls in retrofit so I can display ProgressDialog at common place & handle common response.

I achieved this by creating wrapper like this

public static <T> Observable<T> callApiWrapper(final Context context,
                                                   final boolean shouldShowProgress,
                                                   final String message,
                                                   final Observable<T> source) {

        final ProgressDialog progressDialog = new ProgressDialog(context);
        if (shouldShowProgress) {
            if (!TextUtils.isEmpty(message))
                progressDialog.setMessage(message);
            else
                progressDialog.setMessage(context.getString(R.string.please_wait));
        }


        return source.lift(new Observable.Operator<T, T>() {
            @Override
            public Subscriber<? super T> call(final Subscriber<? super T> child) {
                return new Subscriber<T>() {
                    @Override
                    public void onStart() {
                        super.onStart();

                        if (shouldShowProgress) {
                            new Handler(Looper.getMainLooper()).post(new Runnable() {
                                @Override
                                public void run() {
                                    progressDialog.show();
                                }
                            });
                        }

                        child.onStart();
                    }

                    @Override
                    public void onCompleted() {
                        if (shouldShowProgress && progressDialog.isShowing())
                            progressDialog.dismiss();
                        child.onCompleted();
                    }

                    @Override
                    public void onError(Throwable e) {
                        if (shouldShowProgress && progressDialog.isShowing())
                            progressDialog.dismiss();
                        child.onError(e);
                    }

                    @Override
                    public void onNext(T t) {

                        /*
                            Handle Invalid API response
                         */

                        if (((BaseResponse) t).getStatus() == RestParams.Codes.INVALID_API_KEY) {
                            mCommonDataModel.setApiKey("");
                            getCommonApiService().getApiKey()
                                    .subscribeOn(Schedulers.newThread())
                                    .observeOn(AndroidSchedulers.mainThread())
                                    .subscribe(new Subscriber<ResponseBody>() {
                                        @Override
                                        public void onCompleted() {

                                        }

                                        @Override
                                        public void onError(Throwable e) {

                                        }

                                        @Override
                                        public void onNext(ResponseBody responseBody) {

                                            try {
                                                String response = responseBody.string();
                                                JSONObject jsonObject = new JSONObject(response);
                                                String key = jsonObject.optString("KEY");

                                                if (!TextUtils.isEmpty(key))
                                                    mCommonDataModel.setApiKey(key);

                                                callApiWrapper(context, shouldShowProgress,
                                                        message, source)
                                                        .subscribeOn(Schedulers.newThread())
                                                        .observeOn(AndroidSchedulers.mainThread())
                                                        .subscribe();
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }
                                    });
                        } else {
                            if (shouldShowProgress && progressDialog.isShowing())
                                progressDialog.dismiss();


                            child.onNext(t);
                        }

                    }
                };
            }
        });

    }

In the above code, I check that if I get specific status code like Invalid API KEY, then I am calling an API to get the new API key instead of giving the status directly to original subscriber.

Once I get the new API key successfully, I call the wrapper recursively & try to give the response to original subscriber. But the problem is Original Subscriber is not getting onNext callback

What am I missing here? Is there any other way of achieving what I am trying to do?


Solution

  • Finally I managed to create a wrapper which handles for me the common Progressbar & retry logic in case of Invalid Key API Response. This kind of wrapper might be useful if in many cases. Thanks to @JohnWowUs for his answer which helped me to understand things & implement this wrapper.

    Here's the working code

    private static final int MAX_RETRIES = 2;
    private static int sCurrentRetryAttempt = 0;
    
    /**
         * Common Wrapper for calling API.
         *
         * @param context            context for showing progress dialog
         * @param shouldShowProgress boolean which indicated if progress dialog should be shown or not
         * @param message            message to be shown in progress dialog. if null passed, then "Please wait..." will be shown
         * @param source             original observable
         * @return observable to which observer can subscribe
         */
        public static <T> Observable<T> callApiWrapper(final Context context,
                                                          final boolean shouldShowProgress,
                                                          final String message,
                                                          final Observable<T> source) {
    
            // Progress Dialog
            final ProgressDialog progressDialog = setupProgressDialog(context, shouldShowProgress, message);
            if (progressDialog != null) progressDialog.show();
    
            return source
                    .flatMap(new Func1<T, Observable<T>>() {
                        @Override
                        public Observable<T> call(T t) {
    
                            /*
                             * Check if the response contains invalid key status code.
                             */
    
                            if (t instanceof BaseResponse) {
                                if (((BaseResponse) t).getStatus() == RestParams.Codes.INVALID_API_KEY) {
                                    return Observable.error(new InvalidKeyException("Invalid key"));
                                }
                            }
    
                            /*
                             * We are here, that means, there wasn't invalid key status code.
                             * So we wouldn't like to handle it so just return to original subscriber
                             */
                            if (progressDialog != null && progressDialog.isShowing())
                                progressDialog.dismiss();
    
                            return Observable.just(t);
                        }
                    }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                        @Override
                        public Observable<?> call(Observable<? extends Throwable> observable) {
    
                            return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                                @Override
                                public Observable<?> call(final Throwable throwable) {
                                    if (throwable instanceof InvalidKeyException) {
                                        /*
                                         * Check for retry limit. if we already have retried enough, then
                                         * we should tell the original subscriber about the error as it
                                         * doesn't seems recoverable.
                                         */
                                        if (sCurrentRetryAttempt >= MAX_RETRIES) {
                                            if (progressDialog != null && progressDialog.isShowing())
                                                progressDialog.dismiss();
    
                                            //Resetting the attempts to 0
                                            sCurrentRetryAttempt = 0;
                                            return Observable.error(throwable);
                                        }
    
                                        //Increase the attempt counter
                                        sCurrentRetryAttempt += 1;
                                        return getCommonApiService().getApiKey()
                                                .subscribeOn(Schedulers.newThread())
                                                .observeOn(AndroidSchedulers.mainThread())
                                                .flatMap(new Func1<ResponseBody, Observable<?>>() {
                                                    @Override
                                                    public Observable<?> call(ResponseBody responseBody) {
                                                        try {
                                                            /*
                                                             * Check if we succeed in our attempt to handle
                                                             * invalid key
                                                             */
                                                            if (processApiKey(responseBody)) {
    
                                                                /*
                                                                 * We succeeded in our attempts to handle
                                                                 * invalid api key, so we will return the
                                                                 * original subscriber what it wanted.
                                                                 */
                                                                return callApiWrapper(context,
                                                                        shouldShowProgress, message, source);
                                                            } else
                                                                return Observable.just(throwable);
                                                        } catch (Exception e) {
                                                            /*
                                                             * We are here that means something went wrong,
                                                             * so we will retry silently.
                                                             */
                                                            return Observable.just(throwable);
                                                        }
                                                    }
                                                });
                                    } else {
                                        /*
                                         * For any other error, we are not going to handle right now,
                                         * so just return
                                         */
                                        return Observable.error(throwable);
                                    }
    
                                }
                            });
                        }
                    });
        }
    

    & using this is same as normal like:

    RestClient.callApiWrapper(mContext, true, null,
                    RestClient.getAuthenticationApiService().socialLogIn(name, email, singInVia, mobile, "android", deviceToken))
                    .subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Subscriber<BaseResponse<RegistrationResponse>>() {
    //...
    }