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

Get Reactive

Get Reactive

Given at Droidcon UK 2015

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.

F4cff86f26a0d63a0f5d20494e955c04?s=128

Pascal Welsch

October 29, 2015
Tweet

Transcript

  1. Get Reactive

  2. +Wolfram Rittmeyer @rittmeyerw +Pascal Welsch @passsy

  3. Reactive Extensions

  4. RxJava – a library for composing asynchronous and event-based programs

    using observable sequences for the Java VM. https://github.com/ReactiveX/RxJava
  5. Reactive Extensions

  6. 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));
  7. 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); } });
  8. 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); } });
  9. RxJava in Detail

  10. Creating Observables • Basic ◦ Observable.from() ◦ Observable.just() ◦ Observable.range()

    • Timing based ◦ Observable.interval() ◦ Observable.timer() • Custom ◦ Observable.create()
  11. 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); } } });
  12. 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); } } });
  13. 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; } } // ...
  14. Consuming Observables • subscribe() overloaded • Be careful to not

    leak anything ◦ anonymous inner classes ◦ forgetting to unsubscribe
  15. Processing Chain • Transformers ◦ map, flatMap, scan • Filters

    ◦ filter, throttle, take, skip • Conditions ◦ amb, skipWhile, takeWhile • Combination ◦ concat, zip, combineLatest • Aggregate ◦ count, reduce, toList, toMap
  16. flatMap

  17. combineLatest

  18. zip

  19. Off the Main thread • Schedulers ◦ subscribeOn() ▪ Schedulers.io()

    ▪ Schedulers.computation() ▪ Schedulers.newThread() ▪ Schedulers.fromExecutor() ◦ observeOn ▪ AndroidSchedulers.mainThread()
  20. Error Handling • Observable.onErrorResumeNext() • Observable.onErrorReturn() • yourObserver.onError()

  21. Backpressure • Observable to fast for consumer • Can happen

    anywhere in the chain • Some possible actions: ◦ Throttle ◦ Buffer ◦ request(n) upstream items
  22. Testing Observables • Create special cases ◦ Observable.error() ◦ Observable.empty()

    ◦ Observable.never() • Use TestSubscriber
  23. Debugging Observables • Side effect operators ◦ doOnNext() ◦ doOnSubscribe()

    ◦ … • Hooks ◦ RxJavaObservableExecutionHook ◦ RxJavaSchedulersHook ◦ RxJavaErrorHandler
  24. RxAndroid

  25. 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'
  26. Reactive Android UI

  27. 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(); } });
  28. Make Listeners Reactive final Observable<Void> myButtonClickObservable = Observable.create(subscriber -> {

    myButton.setOnClickListener(v -> { if (!subscriber.isUnsubscribed()) { subscriber.onNext(v); } }); }); myButtonClickObservable .subscribe(v -> triggerAction()); lamda for Observable.OnSubscribe<T> unsubscribing not handled!
  29. 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());
  30. compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' RxView.clicks(myButton) .subscribe(v -> triggerAction()); RxBinding Others and more

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

    RxView, RxTextView, RxAdapter, RxAdapterView, RxAutoCompleteTextView, RxCheckedTextView, RxCompoundButton, RxMenuItem, RxSeekBar, RxViewGroup, RxProgressBar, RxRadioGroup, RxRatingBar, RxRecyclerView, RxRecyclerViewAdapter, RxSearchView, RxToolbar, ... rxbinding-support-v4 rxbinding-appcompat-v7 rxbinding-design rxbinding-recyclerview-v7 rxbinding-leanback-v17
  32. Combine UI events

  33. 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...
  34. Build a registration view! TextView username_tv = (TextView) findViewById(R.id.username); TextView

    password_tv = (TextView) findViewById(R.id.password); TextView fullName_tv = (TextView) findViewById(R.id.fullName); // optional field 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
  35. combineLatest

  36. Build a registration view! TextView username_tv = (TextView) findViewById(R.id.username); TextView

    password_tv = (TextView) findViewById(R.id.password); TextView fullName_tv = (TextView) findViewById(R.id.fullName); // optional field 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
  37. network requests triggered by UI

  38. GitHub repository search Using Retrofit 2 for the network calls

  39. 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; } });
  40. 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()); // [\)\]\}]*
  41. 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()); });
  42. 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()); });
  43. 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()); });
  44. 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
  45. 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!
  46. 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 :(
  47. 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); }
  48. 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
  49. +Wolfram Rittmeyer @rittmeyerw +Pascal Welsch @passsy