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
    Get Reactive

    View Slide

  2. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    +Wolfram Rittmeyer
    @rittmeyerw
    +Pascal Welsch
    @passsy

    View Slide

  3. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Reactive Extensions

    View Slide

  4. @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

    View Slide

  5. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Reactive Extensions

    View Slide

  6. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Simple Observable
    Observable 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));

    View Slide

  7. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Simple Subscriber
    countObservable.subscribe(new Subscriber() {

    @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);

    }

    });

    View Slide

  8. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Simple Subscriber
    countObservable.subscribe(new Subscriber() {

    @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);

    }

    });

    View Slide

  9. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    RxJava in Detail

    View Slide

  10. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Creating Observables
    • Basic
    • Observable.from()
    • Observable.just()
    • Observable.range()
    • Timing based
    • Observable.interval()
    • Observable.timer()
    • Custom
    • Observable.create()

    View Slide

  11. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Creating Observables
    Observable.create(new Observable.OnSubscribe() {

    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);

    }

    }

    });

    View Slide

  12. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Creating Observables
    Observable.create(new Observable.OnSubscribe() {

    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);

    }

    }

    });

    View Slide

  13. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Creating Observables
    Observable.create(new Observable.OnSubscribe() {

    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;

    }

    }

    // ...

    View Slide

  14. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Consuming Observables
    • subscribe() overloaded
    • Be careful to not leak anything
    • anonymous inner classes
    • forgetting to unsubscribe

    View Slide

  15. @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

    View Slide

  16. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    flatMap

    View Slide

  17. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    combineLatest

    View Slide

  18. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    zip

    View Slide

  19. @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

    View Slide

  20. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Off the Main thread
    • Schedulers
    • subscribeOn()
    • Schedulers.io()
    • Schedulers.computation()
    • Schedulers.newThread()
    • Schedulers.fromExecutor()
    • observeOn
    • AndroidSchedulers.mainThread()

    View Slide

  21. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Error Handling
    • Observable.onErrorResumeNext()
    • Observable.onErrorReturn()
    • yourObserver.onError()

    View Slide

  22. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Testing Observables
    • Create special cases
    • Observable.error()
    • Observable.empty()
    • Observable.never()
    • Use TestSubscriber

    View Slide

  23. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Debugging Observables
    • Side effect operators
    • doOnNext()
    • doOnSubscribe()
    • …
    • Hooks
    • RxJavaObservableExecutionHook
    • RxJavaSchedulersHook
    • RxJavaErrorHandler

    View Slide

  24. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    RxAndroid

    View Slide

  25. @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'

    View Slide

  26. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Reactive Android UI

    View Slide

  27. @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();

    }

    });

    View Slide

  28. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Make Listeners Reactive
    final Observable myButtonClickObservable =

    Observable.create(subscriber -> {

    myButton.setOnClickListener(v -> {

    if (!subscriber.isUnsubscribed()) {

    subscriber.onNext(v);

    }

    });

    });


    myButtonClickObservable

    .subscribe(v -> triggerAction());
    unsubscribing not handled!
    lamda for
    Observable.OnSubscribe

    View Slide

  29. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Make Listeners Reactive
    final Observable myButtonClickObservable =

    Observable.create(subscriber -> {

    myButton.setOnClickListener(v -> {

    if (!subscriber.isUnsubscribed()) {

    subscriber.onNext(null);

    }

    });

    });


    myButtonClickObservable

    .delay(100, TimeUnit.MILLISECONDS)

    .observeOn(Schedulers.io())

    .subscribe(v -> triggerAction());

    View Slide

  30. @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, ...

    View Slide

  31. @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

    View Slide

  32. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Combine UI events

    View Slide

  33. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    The login view
    TextView username_tv = (TextView) findViewById(R.id.username);

    TextView password_tv = (TextView) findViewById(R.id.password);


    Observable rxUsername = RxTextView.textChanges(username_tv);
    Observable rxPassword = RxTextView.textChanges(password_tv);
    too easy...

    View Slide

  34. @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 rxUsername = RxTextView.textChanges(username_tv);
    Observable rxPassword = RxTextView.textChanges(password_tv);
    Observable rxFullName = RxTextView.textChanges(fullName_tv);
    Observable.combineLatest(

    rxUsername, rxPassword, rxFullName, RegistrationModel::new)

    .filter(RegistrationModel::isValid)

    .subscribe(LoginActivity::enableSubmitButton);
    never called when fullName not entered

    View Slide

  35. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    combineLatest

    View Slide

  36. @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 rxUsername = RxTextView.textChanges(username_tv);
    Observable rxPassword = RxTextView.textChanges(password_tv);
    Observable 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

    View Slide

  37. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    network requests triggered

    View Slide

  38. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    GitHub repository
    search
    Using Retrofit 2 for the
    network calls

    View Slide

  39. @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;

    }

    });

    View Slide

  40. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    the Android Way
    Callback callback
    = new Callback() {

    @Override public void onFailure(final Throwable t) {

    showEmptyErrorView(t.getMessage());

    }


    @Override
    public void onResponse(
    Response response, Retrofit retrofit) {

    if (response.isSuccess()) {

    showRepositories(response.body().getItems());

    } else {

    try {

    showEmptyErrorView(response.errorBody().string());

    } catch (IOException e) {

    showEmptyErrorView(response.message());
    // [\)\]\}]*

    View Slide

  41. @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());

    });

    View Slide

  42. @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());

    });

    View Slide

  43. @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());

    });

    View Slide

  44. @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: 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

    View Slide

  45. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    without backpressure (2/3)
    11-12 04:24:50.307 V: searching: devoxx
    11-12 04:24:50.667 D: 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: 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: 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: 11-12 04:24:52.077 V: got data

    View Slide

  46. @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: 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: 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: 11-12 04:24:52.670 V: got data
    2.3s until the response is visible

    View Slide

  47. @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

    View Slide

  48. @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: 11-12 04:21:39.131 V: got data
    1.3s until the response is visible

    View Slide

  49. @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: 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

    View Slide

  50. @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: 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: 11-12 04:32:40.582 V: got data
    11-12 04:32:40.612 V: searching: devoxx talks ab

    View Slide

  51. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    with throttleLast and debounce
    11-12 04:32:40.574 D: 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: 11-12 04:32:41.452 V: got data

    View Slide

  52. @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!

    View Slide

  53. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    handle error
    private Observable
    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 :(

    View Slide

  54. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    Mock network requests
    // retrofit 2 interface
    @GET("/search/repositories")

    Observable>
    searchRepositoriesObservable(@Query("q") String name);
    // implementation
    @Override

    public Observable>
    searchRepositoriesObservable(final String name) {

    GitHubResponse response =
    new GitHubResponse<>(testRepo1, testRepo2, testRepoN);

    return Observable.just(response)

    .delay(1000, TimeUnit.MILLISECONDS);

    }

    View Slide

  55. @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

    View Slide

  56. @rittmeyerw, @passsy
    #Devoxx #GetReactive
    +Wolfram Rittmeyer
    @rittmeyerw
    +Pascal Welsch
    @passsy
    Don’t forget to subscribe to our social media streams
    (no error handling required)

    View Slide