androidrx-java2retrywhen

Retry a call with Retrofit 2 and RxJava2 after displaying a dialog


I'm calling an API using Retrofit 2 and RxJava2. If a call fails, in some cases (e.g. no Internet connection), I want to display an error dialog to the user and let him retry.

As I'm using RxJava, I was thinking of using .retryWhen(...) but I don't know how to do that as it needs to wait for the user to press the button on the dialog.

At the moment I display the dialog but it retries before the user presses any button. Plus I would like the call to not be retried when the user presses 'cancel'.

Here is the code I have at the moment:

private void displayDialog(DialogInterface.OnClickListener positive, DialogInterface.OnClickListener negative) {
    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
    builder.setMessage("Unexpected error, do you want to retry?")
            .setPositiveButton("Retry", positive)
            .setNegativeButton("Cancel", negative)
            .show();
}

private Observable<Boolean> notifyUser() {
    final PublishSubject<Boolean> subject = PublishSubject.create();
    displayDialog(
            (dialogInterface, i) -> subject.onNext(true),
            (dialogInterface, i) -> subject.onNext(false)
    );

    return subject;
}

private void onClick() {
    Log.d(TAG, "onClick");
    getData()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .retryWhen(attempts -> {
                return attempts.zipWith(
                        notifyUser(),
                        (throwable, res) -> res);
            })
            .subscribe(
                    s -> {
                        Log.d(TAG, "success");
                    });
}

Solution

  • final PublishSubject<Object> retrySubject = PublishSubject.create();
    
    disposable.add(getData()
        .doOnError(throwable -> enableButton())
        .retryWhen(observable -> observable.zipWith(retrySubject, (o, o2) -> o))
        .subscribeWith(/* do what you want with the result*/)
    }));
    

    When button is clicked you trigger this event:

    retrySubject.onNext(new Object());
    

    As you can see in this Marble diagram:

    enter image description here

    the error is not propagated. The retryWhen operator will indeed handle it and do a proper action. This is the reason why you have to enable (or for example show a Dialog) in doOnError, before retryWhen operator.

    When you don't want to listen anymore for successive retry attempts, you just need to unsubscribe:

    disposable.dispose();
    

    As per your question:

    What should I do if I want to retry only on a specific Exception but not on the other ones?

    You can modify your retryWhen in this way:

    .retryWhen(throwableObservable -> throwableObservable.flatMap(throwable -> {
          if (throwable instanceof TargetException) {
              return Observable.just(throwable).zipWith(retrySubject, (o, o2) -> o);
          } else {
              throw Throwables.propagate(throwable);
          }
    }))
    

    Where Throwables.propagate(throwable) is a Guava util that can be replaced with throw new RuntimeException(throwable);