Slide 1

Slide 1 text

Get Reactive

Slide 2

Slide 2 text

+Wolfram Rittmeyer @rittmeyerw +Pascal Welsch @passsy

Slide 3

Slide 3 text

Reactive Extensions

Slide 4

Slide 4 text

RxJava – a library for composing asynchronous and event-based programs using observable sequences for the Java VM. https://github.com/ReactiveX/RxJava

Slide 5

Slide 5 text

Reactive Extensions

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

RxJava in Detail

Slide 10

Slide 10 text

Creating Observables ● Basic ○ Observable.from() ○ Observable.just() ○ Observable.range() ● Timing based ○ Observable.interval() ○ Observable.timer() ● Custom ○ Observable.create()

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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; } } // ...

Slide 14

Slide 14 text

Consuming Observables ● subscribe() overloaded ● Be careful to not leak anything ○ anonymous inner classes ○ forgetting to unsubscribe

Slide 15

Slide 15 text

Processing Chain ● Transformers ○ map, flatMap, scan ● Filters ○ filter, throttle, take, skip ● Conditions ○ amb, skipWhile, takeWhile ● Combination ○ concat, zip, combineLatest ● Aggregate ○ count, reduce, toList, toMap

Slide 16

Slide 16 text

flatMap

Slide 17

Slide 17 text

combineLatest

Slide 18

Slide 18 text

zip

Slide 19

Slide 19 text

Off the Main thread ● Schedulers ○ subscribeOn() ■ Schedulers.io() ■ Schedulers.computation() ■ Schedulers.newThread() ■ Schedulers.fromExecutor() ○ observeOn ■ AndroidSchedulers.mainThread()

Slide 20

Slide 20 text

Error Handling ● Observable.onErrorResumeNext() ● Observable.onErrorReturn() ● yourObserver.onError()

Slide 21

Slide 21 text

Backpressure ● Observable to fast for consumer ● Can happen anywhere in the chain ● Some possible actions: ○ Throttle ○ Buffer ○ request(n) upstream items

Slide 22

Slide 22 text

Testing Observables ● Create special cases ○ Observable.error() ○ Observable.empty() ○ Observable.never() ● Use TestSubscriber

Slide 23

Slide 23 text

Debugging Observables ● Side effect operators ○ doOnNext() ○ doOnSubscribe() ○ … ● Hooks ○ RxJavaObservableExecutionHook ○ RxJavaSchedulersHook ○ RxJavaErrorHandler

Slide 24

Slide 24 text

RxAndroid

Slide 25

Slide 25 text

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'

Slide 26

Slide 26 text

Reactive Android UI

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Make Listeners Reactive final Observable myButtonClickObservable = Observable.create(subscriber -> { myButton.setOnClickListener(v -> { if (!subscriber.isUnsubscribed()) { subscriber.onNext(v); } }); }); myButtonClickObservable .subscribe(v -> triggerAction()); lamda for Observable.OnSubscribe unsubscribing not handled!

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Combine UI events

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

combineLatest

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

network requests triggered by UI

Slide 38

Slide 38 text

GitHub repository search Using Retrofit 2 for the network calls

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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()); // [\)\]\}]*

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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!

Slide 46

Slide 46 text

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 :(

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

+Wolfram Rittmeyer @rittmeyerw +Pascal Welsch @passsy