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

Get Reactive - Devoxx

Get Reactive - Devoxx

The RxJava hype is still in full swing and almost everything gets Rxified. At least since the stable RxAndroid 1.0 release it’s time to take RxAndroid seriously!

In this session you will learn what RxJava is about, how to use it on Android and how RxAndroid can be used to build maintainable Android Apps from networking with Retrofit2 to UI with the new RxBinding and RxLifecycle libraries powered by MVP.

If you haven’t had the time to Get Reactive, we’ll provide a quick introduction why you should use RxAndroid in your current and next projects.

Pascal Welsch

November 12, 2015
Tweet

More Decks by Pascal Welsch

Other Decks in Programming

Transcript

  1. @rittmeyerw, @passsy #Devoxx #GetReactive RxJava – a library for composing

    asynchronous and event-based programs using observable sequences for the Java VM. https://github.com/ReactiveX/RxJava
  2. @rittmeyerw, @passsy #Devoxx #GetReactive Simple Observable Observable<String> countObservable =
 Observable.from(Arrays.asList(new

    Integer[]{2, 3, 5, 7}))
 .filter(n -> n % 2 != 0)
 .count()
 .map(n -> String.format("%d item(s)", n));
  3. @rittmeyerw, @passsy #Devoxx #GetReactive Simple Subscriber countObservable.subscribe(new Subscriber<String>() {
 @Override


    public void onCompleted() {
 Log.d("get_reactive", "Done!");
 }
 @Override
 public void onError(Throwable e) {
 Log.e("get_reactive", "Yikes!", e);
 }
 @Override
 public void onNext(String s) {
 Log.d("get_reactive", s);
 }
 });
  4. @rittmeyerw, @passsy #Devoxx #GetReactive Simple Subscriber countObservable.subscribe(new Subscriber<String>() {
 @Override


    public void onCompleted() {
 Log.d("get_reactive", "Done!");
 }
 @Override
 public void onError(Throwable e) {
 Log.e("get_reactive", "Yikes!", e);
 }
 @Override
 public void onNext(String s) {
 Log.d("get_reactive", s);
 }
 });
  5. @rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables • Basic • Observable.from()

    • Observable.just() • Observable.range() • Timing based • Observable.interval() • Observable.timer() • Custom • Observable.create()
  6. @rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe<Integer>() {
 public

    void call(Subscriber<? super Integer> s) {
 try {
 Random r = new Random();
 for (int i = 0; i < 10; i++) {
 s.onNext(r.nextInt());
 }
 s.onCompleted();
 }
 catch (Throwable t) {
 s.onError(t);
 }
 }
 });
  7. @rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe<Integer>() {
 public

    void call(Subscriber<? super Integer> s) {
 try {
 Random r = new Random();
 for (int i = 0; i < 10; i++) {
 s.onNext(r.nextInt());
 }
 s.onCompleted();
 }
 catch (Throwable t) {
 s.onError(t);
 }
 }
 });
  8. @rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe<Integer>() {
 public

    void call(Subscriber<? super Integer> s) {
 try {
 Random r = new Random();
 for (int i = 0; i < 10; i++) {
 if (!s.isUnsubscribed()) {
 s.onNext(r.nextInt());
 }
 else {
 break;
 }
 }
 // ...
  9. @rittmeyerw, @passsy #Devoxx #GetReactive Consuming Observables • subscribe() overloaded •

    Be careful to not leak anything • anonymous inner classes • forgetting to unsubscribe
  10. @rittmeyerw, @passsy #Devoxx #GetReactive Processing Chain • Transformers • map,

    flatMap, scan • Filters • filter, throttle, take, skip • Conditions • amb, skipWhile, takeWhile • Combination • concat, zip, combineLatest • Aggregate • count, reduce, toList, toMap
  11. @rittmeyerw, @passsy #Devoxx #GetReactive Backpressure • Observable to fast for

    consumer • Can happen anywhere in the chain • Some possible actions: • Throttle • Buffer • request(n) upstream items
  12. @rittmeyerw, @passsy #Devoxx #GetReactive Off the Main thread • Schedulers

    • subscribeOn() • Schedulers.io() • Schedulers.computation() • Schedulers.newThread() • Schedulers.fromExecutor() • observeOn • AndroidSchedulers.mainThread()
  13. @rittmeyerw, @passsy #Devoxx #GetReactive Testing Observables • Create special cases

    • Observable.error() • Observable.empty() • Observable.never() • Use TestSubscriber
  14. @rittmeyerw, @passsy #Devoxx #GetReactive Debugging Observables • Side effect operators

    • doOnNext() • doOnSubscribe() • … • Hooks • RxJavaObservableExecutionHook • RxJavaSchedulersHook • RxJavaErrorHandler
  15. @rittmeyerw, @passsy #Devoxx #GetReactive What is RxAndroid? // RxAndroid contains

    the AndroidScheduler (main thread)
 // few releases
 compile 'io.reactivex:rxandroid:1.0.1' 
 
 // additionally add specific (latest) version of RxJava
 compile 'io.reactivex:rxjava:1.0.14'
  16. @rittmeyerw, @passsy #Devoxx #GetReactive Make Listeners Reactive // typical Android

    widget listener final Button myButton = (Button) findViewById(R.id.myButton);
 myButton.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(final View v) {
 triggerAction();
 }
 });
  17. @rittmeyerw, @passsy #Devoxx #GetReactive Make Listeners Reactive final Observable<Void> myButtonClickObservable

    =
 Observable.create(subscriber -> {
 myButton.setOnClickListener(v -> {
 if (!subscriber.isUnsubscribed()) {
 subscriber.onNext(v);
 }
 });
 });
 
 myButtonClickObservable
 .subscribe(v -> triggerAction()); unsubscribing not handled! lamda for Observable.OnSubscribe<T>
  18. @rittmeyerw, @passsy #Devoxx #GetReactive Make Listeners Reactive final Observable<Void> myButtonClickObservable

    =
 Observable.create(subscriber -> {
 myButton.setOnClickListener(v -> {
 if (!subscriber.isUnsubscribed()) {
 subscriber.onNext(null);
 }
 });
 });
 
 myButtonClickObservable
 .delay(100, TimeUnit.MILLISECONDS)
 .observeOn(Schedulers.io())
 .subscribe(v -> triggerAction());
  19. @rittmeyerw, @passsy #Devoxx #GetReactive RxBinding compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' RxView.clicks(myButton)
 .subscribe(v ->

    triggerAction()); Others and more RxView, RxTextView, RxAdapter, RxAdapterView, RxAutoCompleteTextView, RxCheckedTextView, RxCompoundButton, RxMenuItem, RxSeekBar, RxViewGroup, RxProgressBar, RxRadioGroup, RxRatingBar, RxRecyclerView, RxRecyclerViewAdapter, RxSearchView, RxToolbar, ...
  20. @rittmeyerw, @passsy #Devoxx #GetReactive compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' RxView.clicks(myButton)
 .subscribe(v -> triggerAction());

    Others and more RxView, RxTextView, RxAdapter, RxAdapterView, RxAutoCompleteTextView, RxCheckedTextView, RxCompoundButton, RxMenuItem, RxSeekBar, RxViewGroup, RxProgressBar, RxRadioGroup, RxRatingBar, RxRecyclerView, RxRecyclerViewAdapter, RxSearchView, RxToolbar, ... RxBinding :rxbinding-support-v4 :rxbinding-appcompat-v7 :rxbinding-design :rxbinding-recyclerview-v7 :rxbinding-leanback-v17
  21. @rittmeyerw, @passsy #Devoxx #GetReactive The login view TextView username_tv =

    (TextView) findViewById(R.id.username);
 TextView password_tv = (TextView) findViewById(R.id.password);
 
 Observable<CharSequence> rxUsername = RxTextView.textChanges(username_tv); Observable<CharSequence> rxPassword = RxTextView.textChanges(password_tv); too easy...
  22. @rittmeyerw, @passsy #Devoxx #GetReactive Build a registration view! TextView username_tv

    = (TextView) findViewById(R.id.username);
 TextView password_tv = (TextView) findViewById(R.id.password); // optional field
 TextView fullName_tv = (TextView) findViewById(R.id.fullName); 
 Observable<CharSequence> rxUsername = RxTextView.textChanges(username_tv); Observable<CharSequence> rxPassword = RxTextView.textChanges(password_tv); Observable<CharSequence> rxFullName = RxTextView.textChanges(fullName_tv); Observable.combineLatest(
 rxUsername, rxPassword, rxFullName, RegistrationModel::new) 
 .filter(RegistrationModel::isValid)
 .subscribe(LoginActivity::enableSubmitButton); never called when fullName not entered
  23. @rittmeyerw, @passsy #Devoxx #GetReactive Build a registration view! TextView username_tv

    = (TextView) findViewById(R.id.username);
 TextView password_tv = (TextView) findViewById(R.id.password); // optional field
 TextView fullName_tv = (TextView) findViewById(R.id.fullName);
 
 Observable<CharSequence> rxUsername = RxTextView.textChanges(username_tv); Observable<CharSequence> rxPassword = RxTextView.textChanges(password_tv); Observable<CharSequence> rxFullName = RxTextView.textChanges(fullName_tv)
 .mergeWith(Observable.just("")); Observable.combineLatest(
 rxUsername, rxPassword, rxFullName, RegistrationModel::new) // triggers now without entering a fullName
 .filter(RegistrationModel::isValid)
 .subscribe(LoginActivity::enableSubmitButton); emits "" after subscribe
  24. @rittmeyerw, @passsy #Devoxx #GetReactive the Android Way searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {


    @Override
 public boolean onQueryTextChange(final String query) {
 if (!TextUtils.isEmpty(query)) {
 mGitApiService.searchRepositories(query).enqueue(callback); }
 return true;
 }
 
 @Override
 public boolean onQueryTextSubmit(final String query) {
 return false;
 }
 });
  25. @rittmeyerw, @passsy #Devoxx #GetReactive the Android Way Callback<GitHubResponse> callback =

    new Callback<GitHubResponse>() {
 @Override public void onFailure(final Throwable t) {
 showEmptyErrorView(t.getMessage());
 }
 
 @Override public void onResponse( Response<GitHubResponse> response, Retrofit retrofit) {
 if (response.isSuccess()) {
 showRepositories(response.body().getItems());
 } else {
 try {
 showEmptyErrorView(response.errorBody().string());
 } catch (IOException e) {
 showEmptyErrorView(response.message()); // [\)\]\}]*
  26. @rittmeyerw, @passsy #Devoxx #GetReactive the RxBeginner solution RxSearchView.queryTextChanges(searchView)
 .filter(charSequence ->

    !TextUtils.isEmpty(charSequence))
 .flatMap(charSequence -> {
 return mGitApiService .searchRepositoriesObservable(charSequence.toString())
 .subscribeOn(Schedulers.io());
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(response -> {
 showRepositories(response.getItems());
 }, throwable -> {
 showEmptyErrorView(throwable.getMessage());
 });
  27. @rittmeyerw, @passsy #Devoxx #GetReactive The RxBeginner solution RxSearchView.queryTextChanges(searchView)
 .filter(charSequence ->

    !TextUtils.isEmpty(charSequence))
 .flatMap(charSequence -> {
 return searchRepositories(charSequence);
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(response -> {
 showRepositories(response.getItems());
 }, throwable -> {
 showEmptyErrorView(throwable.getMessage());
 });
  28. @rittmeyerw, @passsy #Devoxx #GetReactive keep order with concatMap RxSearchView.queryTextChanges(searchView)
 .filter(charSequence

    -> !TextUtils.isEmpty(charSequence))
 .concatMap(charSequence -> {
 return searchRepositories(charSequence);
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(response -> {
 showRepositories(response.getItems());
 }, throwable -> {
 showEmptyErrorView(throwable.getMessage());
 });
  29. @rittmeyerw, @passsy #Devoxx #GetReactive without backpressure (1/3) 11-12 04:24:48.897 V:

    searching: 11-12 04:24:48.916 V: empty view 11-12 04:24:49.581 V: searching: d 11-12 04:24:49.598 V: requesting d 11-12 04:24:49.624 D: --> GET /search/repositories?q=d HTTP/1.1 11-12 04:24:49.741 V: searching: de 11-12 04:24:49.743 V: requesting de 11-12 04:24:49.910 V: searching: dev 11-12 04:24:50.026 V: searching: devo 11-12 04:24:50.155 V: searching: devox 11-12 04:24:50.307 V: searching: devoxx 11-12 04:24:50.667 D: <-- HTTP/1.1 200 OK (1042ms, -1-byte body) 11-12 04:24:50.778 V: got data 11-12 04:24:50.779 D: --> GET /search/repositories?q=de HTTP/1.1 11-12 04:24:50.793 V: requesting dev
  30. @rittmeyerw, @passsy #Devoxx #GetReactive without backpressure (2/3) 11-12 04:24:50.307 V:

    searching: devoxx 11-12 04:24:50.667 D: <-- HTTP/1.1 200 OK (1042ms, -1-byte body) 11-12 04:24:50.778 V: got data 11-12 04:24:50.779 D: --> GET /search/repositories?q=de HTTP/1.1 11-12 04:24:50.793 V: requesting dev 11-12 04:24:51.218 D: <-- HTTP/1.1 200 OK (438ms, -1-byte body) 11-12 04:24:51.223 V: got data 11-12 04:24:51.223 D: --> GET /search/repositories?q=dev HTTP/1.1 11-12 04:24:51.243 V: requesting devo 11-12 04:24:51.728 D: <-- HTTP/1.1 200 OK (504ms, -1-byte body) 11-12 04:24:51.734 V: got data 11-12 04:24:51.741 V: requesting devox 11-12 04:24:51.741 D: --> GET /search/repositories?q=devo HTTP/1.1 11-12 04:24:52.067 D: <-- HTTP/1.1 200 OK (325ms, -1-byte body) 11-12 04:24:52.077 V: got data
  31. @rittmeyerw, @passsy #Devoxx #GetReactive without backpressure (3/3) 11-12 04:24:51.741 V:

    requesting devox 11-12 04:24:51.741 D: --> GET /search/repositories?q=devo HTTP/1.1 11-12 04:24:52.067 D: <-- HTTP/1.1 200 OK (325ms, -1-byte body) 11-12 04:24:52.077 V: got data 11-12 04:24:52.078 D: --> GET /search/repositories?q=devox HTTP/ 1.1 11-12 04:24:52.096 V: requesting devoxx 11-12 04:24:52.360 D: <-- HTTP/1.1 200 OK (282ms, -1-byte body) 11-12 04:24:52.369 V: got data 11-12 04:24:52.369 D: --> GET /search/repositories?q=devoxx HTTP/ 1.1 11-12 04:24:52.663 D: <-- HTTP/1.1 200 OK (293ms, -1-byte body) 11-12 04:24:52.670 V: got data 2.3s until the response is visible
  32. @rittmeyerw, @passsy #Devoxx #GetReactive handle backpressure RxSearchView.queryTextChanges(searchView)
 .filter(charSequence -> !TextUtils.isEmpty(charSequence))

    .throttleLast(100, TimeUnit.MILLISECONDS)
 .debounce(200, TimeUnit.MILLISECONDS)
 .onBackpressureLatest()
 .concatMap(charSequence -> {
 return searchRepositories(charSequence);
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(response -> {
 showRepositories(response.getItems());
 }, throwable -> {
 showEmptyErrorView(throwable.getMessage());
 }); prevents request for every character only one query waiting while request running
  33. @rittmeyerw, @passsy #Devoxx #GetReactive with throttleLast and debounce 11-12 04:21:35.661

    V: searching: 11-12 04:21:35.875 V: empty view 11-12 04:21:37.098 V: searching: d 11-12 04:21:37.261 V: searching: de 11-12 04:21:37.430 V: searching: dev 11-12 04:21:37.572 V: searching: devo 11-12 04:21:37.740 V: searching: devox 11-12 04:21:37.903 V: searching: devoxx 11-12 04:21:38.177 V: requesting devoxx 11-12 04:21:38.186 D: --> GET /search/repositories?q=devoxx HTTP/ 1.1 11-12 04:21:39.024 D: <-- HTTP/1.1 200 OK (837ms, -1-byte body) 11-12 04:21:39.131 V: got data 1.3s until the response is visible
  34. @rittmeyerw, @passsy #Devoxx #GetReactive request while typing 11-12 04:32:37.580 V:

    searching: 11-12 04:32:37.875 V: empty view 11-12 04:32:38.355 V: searching: d 11-12 04:32:38.521 V: searching: de 11-12 04:32:38.573 V: requesting d 11-12 04:32:38.582 D: --> GET /search/repositories?q=d HTTP/1.1 11-12 04:32:38.706 V: searching: dev 11-12 04:32:38.882 V: searching: devo 11-12 04:32:39.147 V: searching: devox 11-12 04:32:39.172 V: requesting devo 11-12 04:32:39.313 V: searching: devoxx 11-12 04:32:39.379 D: <-- HTTP/1.1 200 OK (797ms, -1-byte body) 11-12 04:32:39.457 V: searching: devoxx 11-12 04:32:39.692 V: searching: devoxx t 11-12 04:32:39.784 V: searching: devoxx ta
  35. @rittmeyerw, @passsy #Devoxx #GetReactive with throttleLast and debounce 11-12 04:32:39.964

    V: searching: devoxx tal 11-12 04:32:39.972 V: got data 11-12 04:32:39.975 D: --> GET /search/repositories?q=devo HTTP/1.1 11-12 04:32:40.000 V: requesting devoxx 11-12 04:32:40.089 V: searching: devoxx talk 11-12 04:32:40.185 V: searching: devoxx talks 11-12 04:32:40.264 D: <-- HTTP/1.1 200 OK (289ms, -1-byte body) 11-12 04:32:40.270 V: got data 11-12 04:32:40.271 D: --> GET /search/repositories?q=devoxx%20 HTTP/1.1 11-12 04:32:40.346 V: searching: devoxx talks 11-12 04:32:40.506 V: searching: devoxx talks a 11-12 04:32:40.574 D: <-- HTTP/1.1 200 OK (303ms, -1-byte body) 11-12 04:32:40.582 V: got data 11-12 04:32:40.612 V: searching: devoxx talks ab
  36. @rittmeyerw, @passsy #Devoxx #GetReactive with throttleLast and debounce 11-12 04:32:40.574

    D: <-- HTTP/1.1 200 OK (303ms, -1-byte body) 11-12 04:32:40.582 V: got data 11-12 04:32:40.612 V: searching: devoxx talks ab 11-12 04:32:40.705 V: searching: devoxx talks abo 11-12 04:32:40.831 V: searching: devoxx talks abou 11-12 04:32:40.977 V: searching: devoxx talks about 11-12 04:32:41.278 V: requesting devoxx talks about 11-12 04:32:41.279 D: --> GET /search/repositories?q=devoxx %20talks%20about HTTP/1.1 11-12 04:32:41.451 D: <-- HTTP/1.1 200 OK (172ms, -1-byte body) 11-12 04:32:41.452 V: got data
  37. @rittmeyerw, @passsy #Devoxx #GetReactive handle error RxSearchView.queryTextChanges(searchView)
 .filter(charSequence -> !TextUtils.isEmpty(charSequence))

    .throttleLast(100, TimeUnit.MILLISECONDS)
 .debounce(200, TimeUnit.MILLISECONDS)
 .onBackpressureLatest()
 .concatMap(charSequence -> {
 return searchRepositories(charSequence);
 })
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(response -> {
 showRepositories(response.getItems());
 }, throwable -> {
 showEmptyErrorView(throwable.getMessage());
 }); 1. error not handled 2. handled here 3. completes Observable with error -> no more emits!
  38. @rittmeyerw, @passsy #Devoxx #GetReactive handle error private Observable<GitHubResponse> searchRepositories(CharSequence charSequence)

    {
 return mGitApiService .searchRepositoriesObservable(charSequence.toString())
 .subscribeOn(Schedulers.io())
 .onErrorResumeNext(throwable -> {
 // handle error somehow, change UI
 return Observable.empty();
 });
 } Observable completes without error and without result rx.Single doesn’t support this error handling :(
  39. @rittmeyerw, @passsy #Devoxx #GetReactive Mock network requests // retrofit 2

    interface @GET("/search/repositories")
 Observable<GitHubResponse<Repository>> searchRepositoriesObservable(@Query("q") String name); // implementation @Override
 public Observable<GitHubResponse<Repository>> searchRepositoriesObservable(final String name) {
 GitHubResponse<Repository> response = new GitHubResponse<>(testRepo1, testRepo2, testRepoN);
 return Observable.just(response)
 .delay(1000, TimeUnit.MILLISECONDS);
 }
  40. @rittmeyerw, @passsy #Devoxx #GetReactive cancel requests Call unsubscribe in #onStop();

    mSearchViewSubscription.unsubscribe(); or use github.com/trello/RxLifecycle for it Use a Presenter (MVP) to continue the request during a configuration change Recommendation: Mosby or Nucleus github.com/sockeqwe/mosby github.com/konmik/nucleus
  41. @rittmeyerw, @passsy #Devoxx #GetReactive +Wolfram Rittmeyer @rittmeyerw +Pascal Welsch @passsy

    Don’t forget to subscribe to our social media streams (no error handling required)