Slide 1

Slide 1 text

@rittmeyerw, @passsy #Devoxx #GetReactive Get Reactive

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

@rittmeyerw, @passsy #Devoxx #GetReactive Reactive Extensions

Slide 4

Slide 4 text

@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

Slide 5

Slide 5 text

@rittmeyerw, @passsy #Devoxx #GetReactive Reactive Extensions

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

@rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe() {
 public void call(Subscriber 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

@rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe() {
 public void call(Subscriber 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

@rittmeyerw, @passsy #Devoxx #GetReactive Creating Observables Observable.create(new Observable.OnSubscribe() {
 public void call(Subscriber 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

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

Slide 15

Slide 15 text

@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

Slide 16

Slide 16 text

@rittmeyerw, @passsy #Devoxx #GetReactive flatMap

Slide 17

Slide 17 text

@rittmeyerw, @passsy #Devoxx #GetReactive combineLatest

Slide 18

Slide 18 text

@rittmeyerw, @passsy #Devoxx #GetReactive zip

Slide 19

Slide 19 text

@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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

@rittmeyerw, @passsy #Devoxx #GetReactive RxAndroid

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

@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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

@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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@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

Slide 35

Slide 35 text

@rittmeyerw, @passsy #Devoxx #GetReactive combineLatest

Slide 36

Slide 36 text

@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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

@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

Slide 45

Slide 45 text

@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

Slide 46

Slide 46 text

@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

Slide 47

Slide 47 text

@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

Slide 48

Slide 48 text

@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

Slide 49

Slide 49 text

@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

Slide 50

Slide 50 text

@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

Slide 51

Slide 51 text

@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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

@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

Slide 56

Slide 56 text

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