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

RxJava day to day

RxJava day to day

A summary of RxJava concepts to keep in mind during the development process. A few things about RxJava in Android, advices and tips working with Rx.

Yair Carreno

January 09, 2017
Tweet

More Decks by Yair Carreno

Other Decks in Programming

Transcript

  1. When to use Reactive Programming Processing events: Processing user events

    such as mouse movement and clicks, keyboard typing. Interacting with data sources: API Rest Client, S3 Amazon, Firebase, Legacies, Cloud Storage. Doing batch operations: save data in local cache, register in logs systems, access local disks, integrate third party SDK, Realm database operations. When it is necessary to manipulate and control a stream or flow of information you must think about Reactive Programming, in other words almost always.
  2. Players of Reactive Programming The main actors in reactive programming

    world are: Observable<T>, Observer<T>, Subscriber<T> and the operators (map, filter, flatMap, merge, zip, combineLatest and more). Observable is the main component of Rx, it is agnostic about being synchronous or asynchronous. Observables are lazy by default, meaning it does nothing until it is subscribed to and for this reason they are reusable. In RxJava 2 was introduced a new actor, Flowable<T>, which is very similar to Observable from RxJava 1, but with a key, Flowable is the type that supports backpressure (those situations in which an Observable is emitting items more rapidly than an operator or subscriber can consume them). Recommendation: Subscribe the observable just when it is necessary use it, not before. In other words, turn on the car's engine only when you are going to drive.
  3. Anatomy of rx.Observable rx.Observable<T> represents a flow of data sequence

    or information and in the real world we have many examples like: User interface events, bytes transferred over the network, new orders in online shopping, a tweet, a publish of promotion in facebook, a GET request, etc, etc, etc. A lot of events that model an observable. • Observable<Tweet> tweets • Observable<Double> temperature • Observable<Customer> customers • Observable<HttpResponse> response • Observable<Void> completionCallback
  4. Anatomy of rx.Observable Observable<T> can actually produce three types of

    events: onNext, onCompleted y onError. The specification of reactive extensions clearly states that every Observable can emit an arbitrary number of values optionally followed by completion or error (but not both): OnNext* (OnCompleted | OnError)? Creating an observable with operator just(): static <T> Observable<T> just(T x) { return Observable.create(subscriber -> { subscriber.onNext(x); subscriber.onCompleted(); }); }
  5. The power of Reactive Programming If the heart of Rx

    is the rx.Observable, then we can say that the power of Rx is the ability to combine operators. To know each one of the operators and learning to combine them is what will make you Master Rx. To name a few main groups of operators: • Creation: just, from, range. • Transformation: map, flatMap, concatMap, flatMapIterable, concat. • Combination: merge, zip, combineLatest, withLastestFrom, amb. • Sampling: delay, scan, reduce, collect, debounce. • Filters: distinct, distinctUntilChanged, skip, first, last, takeFirst, takeUntil, takeWhile, elementAt, all, exits, contain, all. In RxJava 2 was introduced a lot of operators, so now it is harder to have to write your own operator (custom operator).
  6. Advice 1. It is a very bad idea to use

    global state modified inside an operator. Observable<Long> progress = transferFile(); LongAdder total = new LongAdder(); progress.subscribe(total::add); //Don't do this! 2. Don't create multiple threads through create operator. Even better, avoid to create observables with create operator. //Don't do this! Observable.create(s -> { //Thread A new Thread(() -> { s.onNext("one"); s.onNext("two"); }).start(); // Thread B new Thread(() -> { s.onNext("three"); s.onNext("four"); }).start(); });
  7. Advice 3. Be careful using filter operators like distinct or

    distinctUntilChanged. For instance, a wrong implementation of equals and hashCode (to compare objects) could generate memory consumption and leaks: //Don't do this! Observable<MyObject> uniqueRandomMyObject = randomMyObject .distinct() .take(10); 4. Avoid to use takeLast(), last() operations in infinite streams. Those kind of operations could generate infinite loops and memory leaks.
  8. Advice 5. Observable should be responsible only for production logic.

    Do not try to implement concurrency, that job is a responsibility of the Schedulers: //Don't do this! Observable<String> obs = Observable.create(subscriber -> { log("Subscribed"); Runnable code = () -> { subscriber.onNext("A"); subscriber.onNext("B"); subscriber.onCompleted(); }; new Thread (code, "Async").start(); }); It is very important to know how Schedulers work in Rx. The next block shows about them.
  9. Advice 6. Always have in mind that the observable is

    lazy and immutable, for instance, the following definitions are different: //In this case, the operations run on “main thread” or “default thread”, the scheduler doesn't have any efect. Observable<String> obs = simple(); obs.subscribeOn(Scheduler A); obs.subscribe(); //In this case, the operations run on the scheduler A. Observable<String> obs = simple(); obs.subscribeOn(Scheduler A) .subscribe();
  10. Advice 7. Some operators have a behavior like transverse component,

    that means that its effect does not interfere with the main flow. They are very useful for logging, analytic, caching, testing, debugging and troubleshooting hard-to-detect crashes.. They are called doOn...() operators and a few of them are: • doOnCompleted() • doOnEach() • doOnError() • doOnNext() • doOnRequest() • doOnSubscribe() • doOnTerminate() • DoOnUnsubscribe() To have in mind those operators and used them.
  11. Advice 8. Use the marble diagrams if you need to

    know the behavior of any operator.
  12. Schedulers • All operations running in the scheduler default (main

    thread). • There are not thread pools. • Not asynchronous emission of events involved.
  13. Schedulers • All operations running in the A scheduler. •

    There are not thread pools. • Not asynchronous emission of events involved.
  14. Schedulers • All operations running in the A scheduler. The

    scheduler closest to the observable has priority. Schedule B is not necessary and instead generates overhead. • There are not thread pools. • Not asynchronous emission of events involved.
  15. Schedulers • Different execution threads are generated for the blocking

    operation. • There are thread pools. • There is asynchronous emission of events involved.
  16. subscribeOn / observeOn Rarely will there be need to control

    the concurrency through subscribeOn, however in Android applications is a key aspect and it is not enough only with subscribeOn, it is necessary to combine this operation with observeOn. In Android the Rx concurrency is composed by the pair of operations subscribeOn and observeOn.
  17. subscribeOn / observeOn In Android if the operation involves the

    UI the most possible is that C scheduler is AndroidSchedulers.mainThread. This scheduler is provided by RxAndroid (io.reactivex: rxandroid) No matter what scheduler you are processing the operations, in the end you deliver the results to the main thread.
  18. subscribeOn / observeOn No matter the subscribeOn position, it can

    be defined anywhere between the observable and subscribe. //The following definitions are the same: obsX.subscribeOn(A) .flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .observerOn(C) .subscribe(); obsX.flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .subscribeOn(A) .observerOn(C) .subscribe(); obsX.flatmap(x -> obsY.blockingOperation().subscribeOn(B)) .filter() .observerOn(C) .subscribeOn(A) .subscribe();
  19. subscribeOn / observeOn It is important the observeOn position, every

    operator defined above observeOn will run on the scheduler defined by subscribeOn (or on the default scheduler in case there is no defined subscribeOn). //In this case map, filter runs over default scheduler y doOnNext runs over A scheduler. obs .map() .filter() .observeOn(A) .doOnNext() .subscribe(); //In this case, map runs over A scheduler, filter runs over B scheduler y doOnNext runs over C scheduler. Obs .map() .observeOn(B) .filter() .observeOn(C) .doOnNext() .subscribeOn(A) .subscribe();
  20. Cases in Rx Case 1: I have an API that

    exposes listeners, callbacks, broadcast receivers or intentServices and I want to convert them to Observables to manage everything in a reactive context. Imagine scenarios where we want: • React to the callbacks provided by the Facebook SDK involved in the login process. • React to the listeners of the Firebase API to integrate with Realtime Database, Authentication, Notifications and other features. • React to a local notification sent through an Android services.
  21. Cases in Rx Domain API nonreactive: //Firebase listener notify changes

    in data class AsyncValueListener implements ValueEventListener { private final AsyncEmitter<DataSnapshot> dataEmitter; AsyncValueListener(AsyncEmitter<DataSnapshot> dataEmitter) { this.dataEmitter = dataEmitter; } @Override public void onDataChange(DataSnapshot dataSnapshot) { dataEmitter.onNext(dataSnapshot); dataEmitter.onCompleted(); } @Override public void onCancelled(DatabaseError databaseError) { dataEmitter.onError(databaseError.toException()); } }
  22. Cases in Rx Domain of the Observable created with the

    API: //Observable based in the API pubic Observable<DataSnapshot> dataChangedObs() { return Observable<DataSnapshot>.fromEmitter( emitter -> reference.addListenerForSingleValueEvent(new AsyncValueListener(emitter)), AsyncEmitter.BackpressureMode.BUFFER); } Domain of the subscription: dataChangedObs .map(... some transformation ...) .filter(... some filter ...) .subscribe( dataChanged -> log("Data changed", dataChanged), Throwable::printStackTrace ); Note: As it is presented in the previous block, when operations involve the UI in RxAndroid it is necessary to define subscribeOn/observeOn, the code snippet from here onwards does not include it.
  23. Cases in Rx Case 2. I have a expensive process

    that is not reactive and I want to make it observable to take it to a reactive context. These are cases where I need to call a synchronous blocking operation, a task that needs to be completed to continue with the flow, for example: • Save a property in Shared Preferences. • Download a file from the network. • Store an audio to SD Card. • Parse XML to JSON file. • Load JSON document into assets. • Send a email.
  24. Cases in Rx Domain of the Observable created with the

    operation: private Data load(int id) { //This operation is going to take some time. } //Observable based in the operation Observable<Data> rxLoad (int id) { return Observable.fromCallable(() -> load(id)); } Domain of the subscription: rxLoad(1) .map(... some transformation ...) .filter(... some filter ...) .subscribe( data -> log("My data", data), Throwable::printStackTrace );
  25. Cases in Rx Case 3. Migrate operations from an existing

    external API that is non-reactive. Suppose that we need to book a flight and then notify the user via email and that for this we need to use the services exposed by an external API that is non- reactive: Flight lookupFlight(String flightNo) { //... } Passenger findPassenger(long id) { //... } Ticket bookTicket (Flight flight,Passenger passenger ) { //... } SmtpResponse sendEmail(Ticket ticket) { //... } Domain API nonreactive:
  26. Cases in Rx Domain of the Observable created with the

    API: Observable<Flight> rxLookupFlight(String flightNo) { return Observable.defer(() -> Observable.just(lookupFlight(flightNo))); } Observable<Passenger> rxFindPassenger(long id) { return Observable.defer(() -> Observable.just(findPassenger(id))); } Through the defer() operator we envelop the non-reactive operation in a lazy observable. Observable<Flight> flight = rxLookupFlight("LOT 783"); Observable<Passenger> passenger = rxFindPassenger(42); Observable<Ticket> ticket = flight .zipWith(passenger, ( f , p ) -> bookTicket( f , p )); .subscribe(this::sendEmail); Domain of the subscription:
  27. Cases in Rx Case 4. Orchestrating a set of operations

    involved in a flow. To show this scenario imagine that we are going to consume an API that uses a token with expiration time, in this case the operations involved in the flow could be: • Check if I have a valid token stored in the shared preferences. • If I have a valid token I use it, if I do not have a valid token I request a new one. • Update the new token in shared preferences. • Consume the API using the valid token. • Get the basic information of a product. • Get the detail information of a product.
  28. Cases in Rx Dominio de Observables involucrados: //Api Reactive like

    Retrofit public interface myAPIService { @Get("api/token") Observable<Token> getToken(); @Get("api/product") Observable<Product> getProduct(String token); @Get("api/detailproduct") Observable<DetailProduct> getDetailProduct(String id); } //SharePreferences Manager public interface myPreferencesManager { Observable<Token> getTokenSaved(); Observable<Void> saveToken(String token); } //Validate token private Observable<Token> validateToken(String token) { if (token != null && !expirated) { return Observable.just(token); } else { return myAPIService.getToken().doOnNext(token -> myPreferencesManager::saveToken); } }
  29. Cases in Rx Domain of the subscription: private void loadDetailProduct()

    { myPreferencesManager.getTokenSaved() .flatMap(token -> validateToken::token) .flatMap(token -> myAPIService::getProduct) .flatMap(product - > myAPIService.getDetailProduct(product.id)) .subscribe( detailProduct -> log("My detail product " + detailProduct), Throwable::printStackTrace ); } • The flatMap operator is the orchestrator. • The getTokenSaved and saveToken services could be created using case 2. • To save in local cache we are using a doOn ... () operation, i.e. saving in cache is not required to continue with the flow. If we wanted it to be mandatory to finish caching first to continue with the flow it would be necessary to change the doOnNext operator by the technique described in case 2.
  30. References Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications by

    Tomasz Nurkiewicz, Ben Christensen. ReactiveX/RxJava Reactive Extensions for the JVM – a library for composing asynchronous and event- based programs using observable sequences for the Java VM.