Reactive Programming

Reactive Programming

Internal talk at Applidium (22 Dec 2015)

2696500a913e29a26f38115f8ea56f71?s=128

Adrien Couque

December 22, 2015
Tweet

Transcript

  1. Rx Reactive Extensions 22 décembre 2015

  2. Problems to solve • Callback hell

  3. Problems to solve • Callback hell • Chaining operations

  4. Problems to solve • Callback hell • Chaining operations

  5. Problems to solve • Callback hell • Chaining operations •

    Threading is hard
  6. Problems to solve • Callback hell • Chaining operations •

    Threading is hard CompletionService Delayed Executor ExecutorService Future RunnableFuture ScheduledExecutorService ScheduledFuture ThreadFactory BrokenBarrierException CountDownLatch CyclicBarrier Exchanger ForkJoinPool ForkJoinWorkerThread Phaser RecursiveAction RecursiveTask ScheduledThreadPoolExecutor Semaphore Thread AsyncTask Loader IntentService
  7. Problems to solve • Callback hell • Chaining operations •

    Threading is hard • Error handling
  8. Example: MyMTV Login Account User User User User User Profile

    Profile Profile Profile Profile Success!
  9. Example: RMN Slideshow: - collection of medias (images and videos)

    - images should be displayed as such - videos should display a preview
  10. Reactive Extensions

  11. Rx: Reactive Extensions • Functional Reactive Programming : 1997 (40

    years after Functional/OOP) • Started by Microsoft for .NET • RxJava: 2012 (Netflix) => ReactiveCocoa, RxAndroid
  12. Observable Pattern

  13. Observable single items multiple items synchronous T getData() Iterable<T> getData()

    Collection<T> getData() asynchronous Future<T> getData() Observable<T> getData()
  14. Subscriber event Iterable (pull) Observable (push) retrieve data T next()

    onNext(T) discover error throws Exception onError(Exception) complete returns onCompleted()
  15. Observable Pattern

  16. Creating an Observable Observable<String> o = Observable.from("a", "b", "c"); def

    list = [5, 6, 7, 8] Observable<Integer> o = Observable.from(list); Observable<String> o = Observable.just("one object");
  17. Creating an Observable Observable.just(t); Observable.just(t1, t2, t3, t4, t5, ...);

    Observable.from(iterable); Observable.range(start, end); Observable.empty(); Observable.error(throwable); Observable.never(); Observable.interval(interval, unit); Observable.timer(delay, period, unit);
  18. Creating an Observable func myJust<E>(element: E) -> Observable<E> { return

    create { observer in observer.on(.Next(element)) observer.on(.Completed) return NopDisposable.instance } } myJust(0) .subscribeNext { n in print(n) }
  19. “Warmth” of an Observable

  20. Operators

  21. Observable Pattern

  22. Visualising operators

  23. map

  24. flatMap

  25. Solving RMN Observable<Media> medias = apiManager.getMediaSlideshow(exposition); Observable<URL> urls = medias.flatMap(media

    -> if (media.isImage()) { return Observable.just(media.getImageUrl()); } else { return apiManager.getYoutubePreviewImage(media); } );
  26. Solving RMN Observable<Media> medias = apiManager.getMediaSlideshow(exposition); Observable<URL> urls = medias.flatMap(media

    -> if (media.isImage()) { return Observable.just(media.getImageUrl()); } else { return apiManager.getYoutubePreviewImage(media); } );
  27. Solving RMN Observable<Media> medias = apiManager.getMediaSlideshow(exposition); Observable<URL> urls = medias.flatMap(media

    -> if (media.isImage()) { return Observable.just(media.getImageUrl()); } else { return apiManager.getYoutubePreviewImage(media); } );
  28. Solving RMN Observable<Media> medias = apiManager.getMediaSlideshow(exposition); Observable<URL> urls = medias.flatMap(media

    -> if (media.isImage()) { return Observable.just(media.getImageUrl()); } else { return apiManager.getYoutubePreviewImage(media); } );
  29. Solving RMN Observable<Media> medias = apiManager.getMediaSlideshow(exposition); Observable<URL> urls = medias.flatMap(media

    -> if (media.isImage()) { return Observable.just(media.getImageUrl()); } else { return apiManager.getYoutubePreviewImage(media); } );
  30. filter

  31. Solving MyMTV Login Account User User User User User Profile

    Profile Profile Profile Profile Success!
  32. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  33. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  34. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  35. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  36. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  37. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  38. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  39. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  40. Solving MyMTV Observable.just(mAccount).map(account -> return account.createUserIfNecessary(context); ).flatMap(account -> return Observable.from(account.getUserList().getUsers());

    ).filter(user -> return user.getProfiles().isEmpty(); ).flatMap(user -> return user.createNewProfile(); ).subscribe(onNext, onError, onCompleted);
  41. merge

  42. merge vs concat

  43. concat example // Our sources Observable<Data> memory = ...; Observable<Data>

    disk = ...; Observable<Data> network = ...; // Retrieve the first source with data Observable<Data> source = Observable .concat(memory, disk, network) .first();
  44. flatMap vs concatMap

  45. switchMap

  46. skip

  47. distinct

  48. distinctUntilChanged

  49. retryWhen

  50. defaultIfEmpty - switchIfEmpty

  51. debounce

  52. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  53. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  54. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  55. debounce

  56. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  57. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  58. switchMap - switchOnNext

  59. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  60. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  61. retryWhen

  62. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  63. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  64. Text field -> search RxTextView.textChanges(searchEditText) .debounce(150, MILLISECONDS) .switchMap(Api::searchItems) .retryWhen(new RetryWithConnectivityIncremental(context,

    5, 15, SECONDS)) .subscribe(updateList, t->showErrorToUser());
  65. timeout

  66. zip

  67. combineLatest

  68. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  69. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  70. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  71. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  72. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  73. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  74. combineLatest example emailChangeObservable = RxTextView.textChangeEvents(email); passwordChangeObservable = RxTextView.textChangeEvents(password); submitButton.setEnabled(false); //

    force-disable the button Observable.combineLatest(emailChangeObservable, passwordChangeObservable,(emailObs, pwdObs) -> { boolean emailCheck = emailObs.text().length() >= 3; boolean passwordCheck = pwdObs.text().length() >= 3; return emailCheck && passwordCheck; }).subscribe(aBoolean -> { submitButton.setEnabled(aBoolean); }); // submit button will only be clickable if both forms have more than 3 characters each
  75. Threading (and more)

  76. subscribeOn / observeOn myObservableServices.retrieveImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

  77. subscribeOn / observeOn

  78. subscribeOn / observeOn <T> Transformer<T, T> applySchedulers() { return observable

    -> observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } Observable.from(someSource) .map(data -> manipulate(data)) .compose(applySchedulers()) .subscribe(data -> doSomething(data));
  79. subscribeOn / observeOn <T> Transformer<T, T> applySchedulers() { return observable

    -> observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } Observable.from(someSource) .map(data -> manipulate(data)) .compose(applySchedulers()) .subscribe(data -> doSomething(data));
  80. <T> Transformer<T, T> applySchedulers() { return observable -> observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread());

    } Observable.from(someSource) .map(data -> manipulate(data)) .compose(applySchedulers()) .subscribe(data -> doSomething(data)); subscribeOn / observeOn
  81. private Object slowBlockingMethod() { ... } public Observable<Object> newMethod() {

    return Observable.defer(() -> Observable.just(slowBlockingMethod())); } defer
  82. Cancelling subscriptions @Override public void onStart() { super.onStart(); mSubscriptions =

    new CompositeSubscription(); mSubscriptions.add(rxBus.toObserverable().subscribe(new Action1<Object>() { @Override public void call(Object event) { // ... }})); } @Override public void onStop() { super.onStop(); mSubscriptions.unsubscribe(); }
  83. Side effect operations observable.doOnSubscribe(action0); observable.doOnUnsubscribe(action0); observable.doOnNext(action1); observable.doOnCompleted(action0); observable.doOnError(action0); observable.doOnTerminate(action0); //

    before termination methods observable.finallyDo(action0); // after termination methods observable.doOnEach(action1); // next, completed or error observable.doOnRequest(action1);
  84. cache

  85. cache Observable<Photo> request = service.getUserPhoto(id).cache(); Subscription sub = request.subscribe(photo ->

    handleUserPhoto(photo)); // ...When the Activity is being recreated... sub.unsubscribe(); // ...Once the Activity is recreated... request.subscribe(photo -> handleUserPhoto(photo));
  86. cache

  87. publish

  88. back pressure

  89. Questions? 22 décembre 2015