在显示对话框后,使用Retrofit 2和RxJava2重试调用。

7
我正在使用Retrofit 2和RxJava2调用API。如果调用失败,例如没有互联网连接等情况,我希望向用户显示错误对话框并让他重试。
由于我正在使用RxJava,我考虑使用.retryWhen(...),但是我不知道该如何做,因为它需要等待用户按下对话框上的按钮。
目前,我显示了对话框,但是在用户按下任何按钮之前就开始重试了。此外,当用户按下“取消”时,我希望不要重试调用。
以下是我目前的代码:
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");
                    });
}

我最近回答了一个类似的问题:https://stackoverflow.com/a/47631107/1584654 - GVillani82
@GVillani82 我尝试了你的例子,有一些问题。#1 如果发布者从未发出信号会发生什么?我们会一直处于挂起重试状态吗?#2 如果我只想在特定异常上重试,而不是其他异常,该怎么办?#3 如果定义了retryWhen,那么就不会再发出任何错误吗? - Eselfar
我的上一个例子实际上有一些错误。无论如何,我重新写了答案并尝试回答你的问题。 - GVillani82
2个回答

10
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*/)
}));

当按钮被点击时,您会触发此事件:

retrySubject.onNext(new Object());

如您在此大理石图中所见:

enter image description here

错误不会被传播。 retryWhen 操作员确实会处理它并执行适当的操作。 这就是为什么您必须在 retryWhen 操作员之前启用(或例如显示对话框)doOnError 的原因。

当您不再想听连续重试尝试时,只需取消订阅即可:

disposable.dispose();

根据您的问题:

如果我只想在特定的异常上重试,而不是在其他异常上重试,该怎么办?

您可以这样修改您的retryWhen

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

使用Guava工具类Throwables.propagate(throwable)的地方可以替换为throw new RuntimeException(throwable);


2
我正在使用 Kotlin,.retryWhen(observable -> observable.zipWith(retrySubject, (o, o2) -> o)) 导致编译错误(可能是我没有理解 lambda 语法的问题),但我用这个更简单的代码使它工作了:.retryWhen{ retrySubject.toFlowable(BackpressureStrategy.LATEST) } - Damn Vegetables
1
@Gvillani82 用 Single 或者 Completable 能否实现这个需求,而不是使用 Observable? - GuilhE
@GuilhE 是的,我已经做过了。你可以在Single中使用 'retryWhen'。 - Vadim Kotov
1
@DamnVegetables 这样你就忽略了错误的 Flowable<Throwable>。如果你想要使用它,可以这样做:retryWhen { retryHandler -> retryHandler.zipWith(retrySubject.toFlowable(BackpressureStrategy.LATEST), BiFunction<Throwable, Any, Any> { t1, t2 -> t2 } ) }。这是一个关于重试 Single 的示例。 - Vadim Kotov

0

对我来说,我正在使用干净架构,将Android代码放入用例或存储库中以显示对话框很困难。因此,由于我正在使用Kotlin,我传递了一个lambda来为我处理这些事情。就像这样:

这是我的完整Presenter:

 class WeatherDetailsPresenter @Inject constructor(var getCurrentWeatherWithForecastUsecase: GetCurrentWithForcastedWeatherUsecase) : BaseMvpPresenter<WeatherDetailsView>() {

    fun presentCurrentAndForcastedWeather(country: String?) {
        getCurrentWeatherWithForecastUsecase.takeUnless { country?.isBlank() == true }?.apply {
            this.countyName = country
            execute(object : DefaultSubscriber<Pair<CurrentWeatherModel, ForecastedWeatherModel>>() {
                override fun onSubscribe(d: Disposable) {
                    super.onSubscribe(d)
                    showLoading(true)
                }

                override fun onNext(t: Pair<CurrentWeatherModel, ForecastedWeatherModel>) {
                    super.onNext(t)
                    view?.showCurrentWeather(t.first)
                    view?.showForcastWeather(t.second)
                }

                override fun onError(e: Throwable) {
                    super.onError(e)
//if i get an error pass in lambda to the dialog
                    myErrorDialog.setretryAction( {this@WeatherDetailsPresenter.presentCurrentAndForcastedWeather(country)})
                }

                override fun onComplete() {
                    super.onComplete()
                    showLoading(false)
                }
            })
        } ?: run { view?.showCountryUnavailable() }
    }

然后在错误对话框 errordialog 中,我这样做(假设它是一个 DialogFragment):

lateinit var retryAction: () -> Unit

override fun onStart() {
        super.onStart()
        dialog.window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        button.setOnClickListener { dismiss();retryAction() }
    }

我的错误处理代码实际上不是这样的,只是为了让你了解如何传递一个lambda表达式。我相信你可以传递自己的函数。在这种情况下,lambda表达式是重试方法本身。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接