📜 ⬆️ ⬇️

Grokay RxJava, part four: Jet Android

In the first , second and third parts, I explained in general terms the RxJava device. You might think: “Great, but how can I do all this useful for me , as a developer for Android?” In the final part of the article, I will provide some information that is practical for you.

RxAndroid


RxAndroid is an RxJava extension written specifically for Android, which includes special straps around RxJava that make your life easier.

First, there is the AndroidSchedulers class, which provides ready-made schedulers for Android-specific threads. Need to run code on a UI thread? No problem - use AndroidSchedulers.mainThread() :
')
 retrofitService.getImage(url) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap)); 

If you have your own Handler , you can create a scheduler associated with it with HandlerThreadScheduler 1 .

Secondly, we have AndroidObservable , providing opportunities for working with the life cycle of some classes from the Android SDK. It has the bindActivity()() and bindFragment() operators that not only automatically use AndroidSchedulers.mainThread() for monitoring, but also stop generating data when your Activity or Fragment starts to complete its work (so you won’t get into a mess trying to change their state when it cannot be done).

 AndroidObservable.bindActivity(this, retrofitService.getImage(url)) .subscribeOn(Schedulers.io()) .subscribe(bitmap -> myImageView.setImageBitmap(bitmap)); 

I also like AndroidObservable.fromBroadcast() , which allows you to create an Observable that works as a BroadcastReceiver . So, for example, you can receive a notification when the network status changes:

 IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); AndroidObservable.fromBroadcast(context, filter) .subscribe(intent -> handleConnectivityChange(intent)); 

Finally, there is a ViewObservable that adds bindings to the View . It, among other things, contains the ViewObservable.clicks() operators if you want to receive a notification whenever a click on the View occurs, and ViewObservable.text() , which is triggered whenever a TextView changes its content.

 ViewObservable.clicks(mCardNameEditText, false) .subscribe(view -> handleClick(view)); 


Retrofit


There is such a remarkable library that supports RxJava, as Retrofit , a popular REST client for Android. Usually, when you define an asynchronous method in it, you use Callback :

 @GET("/user/{id}/photo") void getUserPhoto(@Path("id") int id, Callback<Photo> cb); 

But, if you use RxJava, you can return our friend Observable instead:

 @GET("/user/{id}/photo") Observable<Photo> getUserPhoto(@Path("id") int id); 

After that you can use Observable as soon as you wish, it will be possible not only to obtain data from it, but also to transform it on the fly!

Observable support included in Retrofit also makes it easy to combine several REST requests together. For example, we have two api methods, the first one returns a photo, and the second one - its metadata. We can put the results of these queries together:

 Observable.zip( service.getUserPhoto(id), service.getPhotoMetadata(id), (photo, metadata) -> createPhotoWithData(photo, metadata)) .subscribe(photoWithData -> showPhoto(photoWithData)); 

I showed something similar in the second part (using flatMap() ). Now I wanted to show how easy it is to collect several REST requests into one using a bunch of RxJava + Retrofit.

Old, slow code


The fact that Retrofit can return Observables is great, but what if you have another library that you have never heard of? Or you have some old code that you would like to change without much effort so that he could work with Observable . Simply put, how do you connect the old code with the new one without rewriting everything?

Most often you will only need to use Observable.just() and Observable.from() :

 private Object oldMethod() { ... } public Observable<Object> newMethod() { return Observable.just(oldMethod()); } 

This will work great if oldMethod() executes quickly, but what if it is not? You will block the entire stream, because oldMethod() will be called oldMethod() , and then its result will be passed to Observable.just() .
To get around this problem, you can use the following trick (which I use all the time): wrap the slow code in Observable.defer() :

 private Object slowBlockingMethod() { ... } public Observable<Object> newMethod() { return Observable.defer(() -> Observable.just(slowBlockingMethod())); } 

Now, the Observable you receive will not call slowBlockingMethod() until you subscribe to it.

Life cycle


I left the most difficult part for last. How do we consider the life cycle of an Activity when working with RxJava? There are a couple of problems that make themselves known again and again:

  1. Renew subscription after changing configuration.
    For example, you make a REST request with Retrofit, and want to display its results in a ListView . What if during the execution of the request, the user turns the phone? It would be necessary to resume the execution of the request, but how?
  2. Memory leaks caused by Observables that hold a reference to Context .
    This problem is caused by creating a subscription that somehow keeps the link to the Context (which is not so difficult if you work with Views !) If the Observable does not complete its work on time, at some point you will find that you are not can free up a large amount of memory.

Unfortunately, there are no silver bullets here, but there are some techniques that can simplify your life.



The first problem can be solved by using the caching mechanisms built into RxJava, which allow you to subscribe to / unsubscribe from the same Observable , without repeating its work. In particular, cache() (or replay() ) will continue the previously executed query, even if you have already unsubscribed. This means that you can continue working after the re-creation of the Activity :

 Observable<Photo> request = service.getUserPhoto(id).cache(); Subscription sub = request.subscribe(photo -> handleUserPhoto(photo)); // ... Activity ... sub.unsubscribe(); // ...  Activity  ... request.subscribe(photo -> handleUserPhoto(photo)); 

Notice that we use the same cached request in both cases; thus, the query it performs will be executed only once. Where you save your request is up to you, but, as is the case with all life-cycle decisions, this should be a place that is going through the changes generated by the life cycle (retained fragment, singleton, etc.)



The second problem is solved by properly unsubscribing from subscriptions according to the life cycle. A common solution is to use the CompositeSubscription to store all your subscriptions, and unsubscribe all from them in onDestroy() or onDestroyView() :

 private CompositeSubscription mCompositeSubscription = new CompositeSubscription(); private void doSomething() { mCompositeSubscription.add( AndroidObservable.bindActivity(this, Observable.just("Hello, World!")) .subscribe(s -> System.out.println(s))); } @Override protected void onDestroy() { super.onDestroy(); mCompositeSubscription.unsubscribe(); } 

To simplify your life, you can create a basic Activity / Fragment containing a CompositeSubscription , through which you will later save all your subscriptions, and which will automatically be cleared.

Attention! As soon as you call CompositeSubscription.unsubscribe() , this instance of CompositeSubscription will no longer be available for use (that is, you can, of course, add subscriptions to it, but it will immediately unsubscribe() ) on them! If you want to continue to use the CompositeSubscription in the future, you will have to create a new instance.

To solve both of these problems, we are forced to write additional code, and therefore I very much hope that one fine day a long-awaited genius will come down to us from heaven and will find a way to simplify it all.

Conclusion?


RxJava is a relatively new technology, and for Android even more so, so there will be no conclusion for Android yet. RxAndroid is still under active development, and we (Android programmers) are still trying to figure out how to do well and how badly; There are no generally accepted examples of the excellent use of the RxJava + RxAndroid bundle, and I bet that a year later some of the tips that I’ve given you here will be considered rather eccentric.

In the meantime, I find that RxJava not only simplifies the process of writing code, but also makes it a bit more interesting. If you still don't believe me, let's meet sometime and chat about it over a glass of beer.

I thank Matthias Kay again for his invaluable help in preparing this article, and I urge everyone to join it to make RxAndroid even cooler!



1 AndroidSchedulers.mainThread() uses within itself HandlerThreadScheduler .

Note translator: I thank user Artem_zin for his help in translating a couple of places that caused me difficulty; if it were not for him and his extensive knowledge of RxJava, I would be stuck for a long time.

Source: https://habr.com/ru/post/265997/


All Articles