Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxJava: Tapping Retry

RxJava: Tapping Retry

In this talk, I am explaining how RxJava retryWhen operator works, how to use it and a few pitfalls in a real usage.

Besides, I am suggesting screen transitions also can be handled with retryWhen in Android app - say, showing Login screen when receiving 401 Unauthorized and then resume the task after login succeeded.

Links:
- ReactiveX - Retry operator

http://reactivex.io/documentation/operators/retry.html
- ReactiveX - Repeat operator

http://reactivex.io/documentation/operators/repeat.html
- RxJava's repeatWhen and retryWhen, explained

http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/
- Build Your Custom Observable/Operator

https://speakerdeck.com/hkurokawa/operator
- RxNearby
https://github.com/hkurokawa/RxNearby

Hiroshi Kurokawa

February 25, 2016
Tweet

More Decks by Hiroshi Kurokawa

Other Decks in Technology

Transcript

  1. REPEAT AND RETRY ▸ repeat() ▸ resubscribes when it receives

    onCompleted() ▸ retry() ▸ resubscribes when it receives onError()
  2. MORE REQUIREMENTS? ▸ You can specify when it should resubscribe

    with repeatWhen() or retryWhen() ▸ notificationHandler defines when it should resubscribe
  3. MORE REQUIREMENTS? ▸ You can specify when it should resubscribe

    with repeatWhen() or retryWhen() ▸ notificationHandler defines when it should resubscribe Observable.create(new OnSubscribeThrowError()) .retryWhen(notificationHandler) .subscribe(i -> { // do something }, System.err::println);
  4. MORE REQUIREMENTS? ▸ You can specify when it should resubscribe

    with repeatWhen() or retryWhen() ▸ notificationHandler defines when it should resubscribe Observable.create(new OnSubscribeThrowError()) .retryWhen(notificationHandler) .subscribe(i -> { // do something }, System.err::println); final Func1<? super Observable<? extends Throwable>, ? extends Observable<?>>
  5. MORE REQUIREMENTS? ▸ You can specify when it should resubscribe

    with repeatWhen() or retryWhen() ▸ notificationHandler defines when it should resubscribe Observable.create(new OnSubscribeThrowError()) .retryWhen(notificationHandler) .subscribe(i -> { // do something }, System.err::println); ? extends Observable<?> call(Observable<? extends Throwable> observable)
  6. MORE REQUIREMENTS? ▸ You can specify when it should resubscribe

    with repeatWhen() or retryWhen() ▸ notificationHandler defines when it should resubscribe Observable.create(new OnSubscribeThrowError()) .retryWhen(notificationHandler) .subscribe(i -> { // do something }, System.err::println); Observable<?> call(Observable<Throwable> observable)
  7. NOTIFICATION HANDLER ▸ Argument: Observable<Throwable> ▸ Describes what kind of

    Error happens ▸ Return: Observable<?> ▸ Resubscribes when it emits an event ▸ The event could be anything! Just a tap ▸ If its onError() or onCompleted() is called, just finish Observable<?> call(Observable<Throwable> observable) Note: The returned Observable has to use the argument Observable as its source
  8. EXAMPLE Observable.create(new OnSubscribeThrowError()) .retryWhen( ) .subscribe(i -> { }, System.err::println);

    observable -> observable.flatMap(throwable -> { if (throwable instanceof RecoverableException) return Observable.just(null); return Observable.error(throwable); })
  9. EXAMPLE Observable.create(new OnSubscribeThrowError()) .retryWhen( ) .subscribe(i -> { }, System.err::println);

    observable -> observable.flatMap(throwable -> { if (throwable instanceof RecoverableException) return Observable.just(null); return Observable.error(throwable); })
  10. EXAMPLE Observable.create(new OnSubscribeThrowError()) .retryWhen( ) .subscribe(i -> { }, System.err::println);

    observable -> observable.flatMap(throwable -> { if (throwable instanceof RecoverableException) return Observable.just(null); return Observable.error(throwable); })
  11. EXAMPLE Observable.create(new OnSubscribeThrowError()) .retryWhen( ) .subscribe(i -> { }, System.err::println);

    observable -> observable.flatMap(throwable -> { if (throwable instanceof RecoverableException) return Observable.just(null); return Observable.error(throwable); }) private static class OnSubscribeThrowError implements Observable.OnSubscribe<Integer> { private int count; @Override public void call(Subscriber<? super Integer> subscriber) { System.out.printf("Subscribe count: %d\n", ++count); if (count < 3) subscriber.onError(new RecoverableException()); else subscriber.onError(new UnrecoverableException()); } }
  12. EXAMPLE Observable.create(new OnSubscribeThrowError()) .retryWhen( ) .subscribe(i -> { }, System.err::println);

    observable -> observable.flatMap(throwable -> { if (throwable instanceof RecoverableException) return Observable.just(null); return Observable.error(throwable); }) private static class OnSubscribeThrowError implements Observable.OnSubscribe<Integer> { private int count; @Override public void call(Subscriber<? super Integer> subscriber) { System.out.printf("Subscribe count: %d\n", ++count); if (count < 3) subscriber.onError(new RecoverableException()); else subscriber.onError(new UnrecoverableException()); } } Return an unrecoverable exception only after the first two calls
  13. EXAMPLE private static class OnSubscribeThrowError implements Observable.OnSubscribe<Integer> { private int

    count; @Override public void call(Subscriber<? super Integer> subscriber) { System.out.printf("Subscribe count: %d\n", ++count); if (count < 3) subscriber.onError(new RecoverableException()); else subscriber.onError(new UnrecoverableException()); } } Subscribe count: 1
 Subscribe count: 2
 Subscribe count: 3
 Sample$UnrecoverableException ▸ Output
  14. TEXT SOME USES ▸ Delayed Retry ▸ Resubscribe a limited

    number of times with zip and range retryWhen(observable -> observable.flatMap(throwable -> Observable.timer(5, TimeUnit.SECONDS))) retryWhen(observable -> observable.zipWith(Observable.range(1, 3), (n, i) -> i))
  15. TEXT SOME USES ▸ Delayed Retry ▸ Resubscribe a limited

    number of times with zip and range ▸ Exponential backoff retryWhen(observable -> observable.flatMap(throwable -> Observable.timer(5, TimeUnit.SECONDS))) retryWhen(observable -> observable.zipWith(Observable.range(1, 3), (n, i) -> i)) retryWhen(observable -> observable.zipWith(Observable.range(1, 3), (n, i) -> i) .flatMap(retryCount -> Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS))
  16. TEXT QUIZ 1 ▸ What happens? getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable ->

    observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  17. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  18. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  19. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  20. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  21. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  22. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  23. TEXT QUIZ 1 ▸ What happens? private static Observable<String> getAccessToken()

    { String accessToken = getCache(); if (accessToken != null) { return Observable.just(accessToken); } return getNewAccessToken(); } getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  24. TEXT QUIZ 1 ANSWER ▸ What happens? ▸ The old

    token is always returned at the retrial getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  25. TEXT QUIZ 1 ANSWER ▸ What happens? ▸ The old

    token is always returned at the retrial ▸ Infinite loop getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  26. TEXT QUIZ 1 ANSWER ▸ What happens? ▸ The old

    token is always returned at the retrial ▸ Infinite loop ▸ The source Observable should be unique getAccessToken() .flatMap(Sample::doSomethingWithToken) .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof TokenExpiredException) { clearCache(); return Observable.just(null); } return Observable.error(throwable); }))
  27. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  28. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  29. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  30. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? ▸ When resubscribed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  31. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  32. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? main final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  33. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? main <= where subject.onNext() is called final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  34. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? main <= where subject.onNext() is called final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); Re-subscription is executed within the retryWhen() operator Thus, the subscribeOn() below retryWhen() does not take effect
  35. TEXT QUIZ 2 ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? ▸ When resubscribed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribeOn(Schedulers.newThread()) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  36. TEXT QUIZ 2’ ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? ▸ When resubscribed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .subscribeOn(Schedulers.newThread()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  37. TEXT QUIZ 2’ ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .subscribeOn(Schedulers.newThread()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  38. TEXT QUIZ 2’ ▸ In which thread is the MyBackgroundOnSubscribe

    executed? ▸ At the 1st time? RxNewThreadScheduler-1 ▸ When resubscribed? RxNewThreadScheduler-1 final PublishSubject<Void> subject = PublishSubject.create(); Observable.create(new MyBackgroundOnSubscribe()) .subscribeOn(Schedulers.newThread()) .retryWhen(observable -> observable.flatMap(th -> subject)) .subscribe(System.out::println, System.err::println); subject.onNext(null);
  39. TEXT QUIZ 2 ANSWER ▸ In which thread is the

    MyBackgroundOnSubscribe executed? ▸ At the 1st time? ▸ The thread specified with subscribeOn() ▸ When resubscribed? ▸ Depends on the thread where onNext() is called and the position of subscribeOn() ▸ If necessary, use Observable#retryWhen(notificationHandler, scheduler) ▸ Read OnSubscribeRedo.java code!
  40. REFERENCE ▸ ReactiveX - Retry operator
 http://reactivex.io/documentation/operators/retry.html ▸ ReactiveX -

    Repeat operator
 http://reactivex.io/documentation/operators/repeat.html ▸ RxJava's repeatWhen and retryWhen, explained
 http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and- retrywhen-explained/ ▸ Build Your Custom Observable/Operator
 https://speakerdeck.com/hkurokawa/operator
  41. TEXT EXAMPLE private PublishSubject<Void> subject; private void onDoActionA() { apiClient.doActionA()

    .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof HttpException && ((HttpException) th).code() == 401) { startLoginActivity(); subject = PublishSubject.create(); return subject; } return Observable.error(throwable); })) .subscribe(data -> { // do something }, System.err::println); }
  42. TEXT EXAMPLE private PublishSubject<Void> subject; private void onDoActionA() { apiClient.doActionA()

    .retryWhen(observable -> observable.flatMap(throwable -> { if (throwable instanceof HttpException && ((HttpException) th).code() == 401) { startLoginActivity(); subject = PublishSubject.create(); return subject; } return Observable.error(throwable); })) .subscribe(data -> { // do something }, System.err::println); }
  43. TEXT EXAMPLE @Override public void onActivityResult(int requestCode, int resultCode, Intent

    data) { switch (requestCode) { case REQ_CODE_LOGIN: if (resultCode == Activity.RESULT_OK) { subject.onNext(user); } else { subject.onError(new LoginCanceledException()); } return; } super.onActivityResult(requestCode, resultCode, data); } if (throwable instanceof HttpException && ((HttpException) th).code() == 401) { startLoginActivity(); subject = PublishSubject.create(); return subject; } return Observable.error(throwable);
  44. TEXT EXAMPLE @Override public void onActivityResult(int requestCode, int resultCode, Intent

    data) { switch (requestCode) { case REQ_CODE_LOGIN: if (resultCode == Activity.RESULT_OK) { subject.onNext(user); } else { subject.onError(new LoginCanceledException()); } return; } super.onActivityResult(requestCode, resultCode, data); } if (throwable instanceof HttpException && ((HttpException) th).code() == 401) { startLoginActivity(); subject = PublishSubject.create(); return subject; } return Observable.error(throwable);
  45. REFERENCE ▸ See this repository ▸ It takes advantage of

    retryWhen() to handle permission request