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

RxJava for Android Developers (Austin Droids Meetup)

John Petitto
January 13, 2016

RxJava for Android Developers (Austin Droids Meetup)

The principles of Reactive Programming will be introduced, using RxJava and other related libraries as a vehicle.

Specific examples targeting the Android platform will be demonstrated.

No prior knowledge of Reactive Programming or RxJava is needed.

Presented at http://www.meetup.com/Austin-Android/events/226870556/

Example code: https://github.com/jpetitto/rxjava-android-example

Written narrative: http://johnpetitto.com/rxjava-android-devs/

John Petitto

January 13, 2016
Tweet

Other Decks in Programming

Transcript

  1. Android Development Deals with lots of asynchronous programming • Can’t

    block the main thread • I/O (network, storage access) • Other long running operations
  2. Traditional Android Developer’s Toolbox • Lots of callbacks • AsyncTask

    of course • Handlers + Loopers • Fall back to java.util.concurrent from time to time • Threads? None of these help address all three issues
  3. So what is RxJava, anyways? RxJava is a library for

    producing, composing and consuming asynchronous streams. It provides powerful abstractions for concurrency and makes error handling a breeze.
  4. Observables (Producers) Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super

    String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("Austin Droids!"); subscriber.onCompleted(); } });
  5. enum Version { JELLY_BEAN, KIT_KAT, LOLLIPOP, MARSHMALLOW } public List<Version>

    getSupportedVersions() { return Arrays.asList(KIT_KAT, LOLLIPOP, MARSHMALLOW); }
  6. Subscribers (Consumers) Observable.defer(() -> Observable.from(getSupportedVersions())) .subscribe(version -> print(version.toString()), throwable ->

    print("Error: " + throwable.getMessage()), () -> print("Don't see your phone's version? Time to upgrade!"));
  7. Subscribers (Consumers) Observable.defer(() -> Observable.from(getSupportedVersions())) .subscribe(new Subscriber<Version>() { @Override public

    void onCompleted() { print("Don't see your phone's version? Time to upgrade!"); } @Override public void onError(Throwable e) { print("Error: " + e.getMessage()); } @Override public void onNext(Version version) { print(version.toString()); } });
  8. public List<String> getDevices(Version version) { switch (version) { case KIT_KAT:

    return Arrays.asList("Samsung Galaxy S3", "LG Optimus G"); case LOLLIPOP: return Arrays.asList("Nexus 4", "Samsung Galaxy S4"); case MARSHMALLOW: return Arrays.asList("Nexus 5", "Nexus 6"); default: return Collections.emptyList(); } }
  9. Operators Operators for... • Transforming ◦ map, flatMap, concatMap, scan,

    buffer, cast • Filtering ◦ filter, take, skip, first, last, distinct • Combining ◦ zip, merge, mergeDelayError, startWith • Creating ◦ create, just, from, defer, interval, range • And more! ◦ cache, materialize, delay, doOnNext, ...
  10. Operators And if you’re still not happy… Implement your own

    and apply it with the lift() operator or…
  11. Operators And if you’re still not happy… Implement your own

    and apply it with the lift() operator or… apply multiple operators with compose()
  12. Schedulers (Multithreading) • Abstraction for concurrency • Determines the how...

    ◦ Thread, Thread Pool, Executor, etc… • … and the when ◦ Either now or in the future
  13. Schedulers (Multithreading) Observable.from(getSupportedVersions()) .filter(version -> version != KIT_KAT) .observeOn(Schedulers.computation()) .map(version

    -> expensiveComputation(version)) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.from(executor)) .subscribe();
  14. Schedulers (Multithreading) Observable.from(getSupportedVersions()) .filter(version -> version != KIT_KAT) .observeOn(Schedulers.computation()) .map(version

    -> expensiveComputation(version)) .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.from(executor)) .subscribe(); compile 'io.reactivex:rxandroid:1.1.0'
  15. Since we all diligently test our code, you probably want

    to know how to test an async stream
  16. Testing List<Version> results = new ArrayList<>(); getSupportedVersions() .toBlocking() .subscribe(version ->

    results.add(version)); assertEquals(Arrays.asList(KIT_KAT, LOLLIPOP, MARSHMALLOW), results); This works, but there’s a better way
  17. Testing TestScheduler testScheduler = Schedulers.test(); TestSubscriber<Long> testSubscriber = new TestSubscriber<>();

    Observable.interval(2, TimeUnit.SECONDS, testScheduler) .subscribe(testSubscriber); testSubscriber.assertNoValues(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); testSubscriber.assertValue(0L); Time Warp
  18. Testing TestScheduler testScheduler = Schedulers.test(); TestSubscriber<Long> testSubscriber = new TestSubscriber<>();

    Observable.interval(2, TimeUnit.SECONDS, testScheduler) .subscribe(testSubscriber); testSubscriber.assertNoValues(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); testSubscriber.assertValue(0L);
  19. Testing TestScheduler testScheduler = Schedulers.test(); TestSubscriber<Long> testSubscriber = new TestSubscriber<>();

    Observable.interval(2, TimeUnit.SECONDS, testScheduler) .subscribe(testSubscriber); testSubscriber.assertNoValues(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); testSubscriber.assertValue(0L);
  20. Testing TestScheduler testScheduler = Schedulers.test(); TestSubscriber<Long> testSubscriber = new TestSubscriber<>();

    Observable.interval(2, TimeUnit.SECONDS) .observeOn(testScheduler) .subscribe(testSubscriber); testSubscriber.assertNoValues(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); testSubscriber.assertValue(0L);
  21. Testing TestScheduler testScheduler = Schedulers.test(); TestSubscriber<Long> testSubscriber = new TestSubscriber<>();

    Observable.interval(2, TimeUnit.SECONDS) .observeOn(testScheduler) .subscribe(testSubscriber); testSubscriber.assertNoValues(); testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); testSubscriber.assertValue(0L);
  22. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage()));
  23. Searching compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence

    -> charSequence.length() > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage()));
  24. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Move off of main thread for search
  25. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Only perform search after 1 second of no change
  26. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Ignore empty search bar
  27. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Make network call with Retrofit
  28. Network Call Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create())

    .build(); LruCache<String, SearchResult> cache = new LruCache<>(5 * 1024 * 1024); // 5MiB GitHubInteractor interactor = new GitHubInteractor(retrofit, cache);
  29. Network Call Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create())

    .build(); LruCache<String, SearchResult> cache = new LruCache<>(5 * 1024 * 1024); // 5MiB GitHubInteractor interactor = new GitHubInteractor(retrofit, cache); compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta3'
  30. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Cancel previous network call (if on-going)
  31. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Sanitize results for consumption
  32. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Post results back on main thread
  33. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Kick off work Starts on main thread since we’re in MainActivity
  34. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Update RecylerView adapter
  35. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Handle error events
  36. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Manage resources
  37. Searching subscription = RxTextView.textChanges(searchBar) .observeOn(Schedulers.io()) .debounce(1, TimeUnit.SECONDS) .filter(charSequence -> charSequence.length()

    > 0) .switchMap(charSequence -> interactor.searchUsers(charSequence.toString())) .flatMap(searchResult -> Observable.from(searchResult.getItems()).limit(20).toList()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchItems -> adapter.refreshItems(searchItems), throwable -> Log.e(TAG, throwable.getMessage())); Manage resources
  38. What’s Coming • The Single API ◦ Currently marked @Beta

    ◦ Simpler semantics due to single value ◦ Suitable for common request/response scenarios ◦ Composable with Observables • RxJava 2.0 ◦ Also in beta ◦ Takes advantage of Java 8 features ◦ Follows the Reactive Streams spec (http://www.reactive-streams.org/) ◦ 1.x will still be supported for us Android devs
  39. Additional Resources • ReactiveX http://reactivex.io/ • RxJava https://github.com/ReactiveX/RxJava • RxAndroid

    https://github.com/ReactiveX/RxAndroid • RxBinding https://github.com/JakeWharton/RxBinding • RxMarbles http://rxmarbles.com/