📜 ⬆️ ⬇️

Basics of reactive programming for Android on a practical example

1. Introduction to reactive programming


Developing a complex Android application with many network connections, user interaction and animation means writing code that is full of nested callbacks. And as the project progresses, such code becomes not only cumbersome and difficult to understand, but also difficult to develop, maintain, and is subject to many subtle errors.

ReactiveX or functional reactive programming offers an alternative approach that can significantly reduce application code and create elegant, understandable applications for managing asynchronous tasks and events. In reactive programming, the consumer responds to the data as it comes in and propagates the changes of the event to the registered observers.

RxJava is an open-source Java implementation of ReactiveX. The basic building blocks of a reactive code are Observables and Subscribers. More information about the basic framework can be found in the article Grokay * RxJava, part one: the basics .
')
RxAndroid is an extension to RxJava that allows the scheduler to run code in the main and additional threads of the Android application and provides the transfer of results from the created additional threads to the main one for aggregation and interaction with the user interface.
In order to more fully understand the basic principles of reactive programming, consider a practical example for the Android platform. And let's start with setting up the environment for development.

2. Preparing the environment


We connect the main libraries and assign dependencies in the dependencies {} section of the buil.gradle configuration file:
dependencies { compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.6' } 

We include support for lambda expressions - we use the new features of the Java 8 language on the Android N platform. To use the features of the Java 8 language, you also need to connect the new Jack compiler, for which you add to the build.gradle file:
 android { ... defaultConfig { ... jackOptions { enabled true } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } 

Note: Jack is only supported in Android Studio 2.1 and you also need to upgrade to JDK 8.

When making changes in the gradle configuration file, a warning appears about the need to synchronize the project and to apply all changes click the Sync Now link in the top right.

3. Create a basic example.


Due to the fact that the use of RxAndroid in most cases is associated with projects with multi-thread processing of network connections - consider a simple example of processing the results of site parsing.
To display the results, create a simple layout:
 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" ...> <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/scrollView" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/textView" /> </ScrollView> </RelativeLayout> 

For parsing, create a simple WebParsing class with two getURLs and getTitle methods:
 public class WebParsing { public List<String> getURLs(String url) { Document doc; List<String> stringList = new ArrayList<>(); try { doc = Jsoup.connect(url).get(); Elements select = doc.select("a"); for (Element element : select) { stringList.add(element.attr("href")); } } catch (IOException e) { e.printStackTrace(); return null; } return stringList; } } 

 public String getTitle(String url) { String title; try { Document doc = Jsoup.connect(url).get(); title = doc.title(); } catch (MalformedURLException mue) { mue.printStackTrace(); return null; } catch (HttpStatusException hse) { hse.printStackTrace(); return null; } catch (IOException e) { e.printStackTrace(); return null; } catch (IllegalArgumentException iae) { iae.printStackTrace(); return null; } return title; } 

The getURLs method scans the site contents and returns a list of all found links, and the getTitle method returns the site Title by reference.

4. We connect the reactivity


In order to use the features of RxAndroid based on the above methods, we will create two corresponding Observables:
 Observable<List<String>> queryURLs(String url) { WebParsing webParsing = new WebParsing(); return Observable.create( new Observable.OnSubscribe<List<String>>() { @Override public void call(Subscriber<? super List<String>> subscriber) { subscriber.onNext(webParsing.getURLs(url)); subscriber.onCompleted(); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); } 

 Observable<String> queryTitle(String url) { WebParsing webParsing = new WebParsing(); return Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext(webParsing.getTitle(url)); subscriber.onCompleted(); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); } 

The first Observable will generate a list of URL links found on the site, the second will generate a Title. Let us examine the example of the first method in detail and line by line:
  1. Observable <List> queryURLs (String url) - the string declares the Observable method, which takes as an input parameter a link to the site for parsing and returns the result of the parsing as a list of <List> links from the specified site;
    WebParsing webParsing = new WebParsing () - creates a variable to access our parsing functions;
    return Observable.create - creates an Observable that returns a list of links;
    new Observable.OnSubscribe <List> () - the string declares the OnSubscribe interface with one method (see below) that will be called when subscribing;
    public void call (Subscriber <? super List> subscriber) - overloads the call method, which will be called after the Subscriber subscription;
    subscriber.onNext (webParsing.getURLs (url)) - calls the onNext method for transmitting Subscriber data whenever data is generated. This method takes as a parameter the object emitted by the Observable;
    subscriber.onCompleted () - Observable calls the onCompleted () method after it calls onNext one last time, if no errors were detected;
    subscribeOn (Schedulers.io ()) - the subscribeOn method subscribes all Observable upstream to Schedulers.io ();
    observeOn (AndroidSchedulers.mainThread ()) - the observeOn method allows you to get results in the main application stream.

5. Start the first reactive application.


So, Observables are created, we will implement the simplest example based on the first method above, which will display a list of site links:
 public void example0(final TextView textView, String url) { queryURLs(url) .subscribe(new Action1<List<String>>() { @Override public void call(List<String> urls) { for (String url: urls) { String string = (String) textView.getText(); textView.setText(string + url + "\n\n"); } } }); } 

We wrap our implemented example in the MainExample class and call it in MainActivity:
 public class MainActivity extends AppCompatActivity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); MainExample mainExample = new MainExample(); mainExample.example0(textView, "https://yandex.ru/"); } } 

6. Increase reactivity - using operators


Observable can transform the output using operators and it can be used between the Observable and Subscriber to manipulate data. There are a lot of operators in RxJava, so for a start we will consider the most demanded ones.
And let's start by getting rid of the cycle in the subscriber and forcing the observer to consistently emit the data of the resulting link array, and the from () operator will help us with this:
 public void example1(final TextView textView, String url) { queryURLs(url) .subscribe(new Action1<List<String>>() { @Override public void call(List<String> urls) { Observable.from(urls) .subscribe(new Action1<String>() { @Override public void call(String url) { String string = (String) textView.getText(); textView.setText(string + url + "\n\n"); } }); } }); } 

It looks not quite nice and a bit confusing, so apply the following flatMap () operator, which accepts data emitted by one Observable and returns data emitted by another Observable, thus substituting one Observable for another:
 public void example2(final TextView textView, String url) { queryURLs(url) .flatMap(new Func1<List<String>, Observable<String>>() { @Override public Observable<String> call(List<String> urls) { return Observable.from(urls); } }) .subscribe(new Action1<String>() { @Override public void call(String url) { String string = (String) textView.getText(); textView.setText(string + url + "\n\n"); } }); } 

In the next step, we will still unload our Subscriber and use the map () operator, through which we can convert one data element to another. The map () operator can also convert data and generate data of the type we need, which is different from the original one. In our case, the observer will generate a list of strings, and the subscriber will only display them on the screen:
 public void example3(final TextView textView, String url) { queryURLs(url) .flatMap(new Func1<List<String>, Observable<String>>() { @Override public Observable<String> call(List<String> urls) { return Observable.from(urls); } }) .map(new Func1<String, String>() { @Override public String call(String url) { return textView.getText() + url + "\n\n"; } }) .subscribe(new Action1<String>() { @Override public void call(String url) { textView.setText(url); } }); } 

We considered the main features and now it's time to use lambdas to simplify our code:
 queryURLs(url) .flatMap(urls -> Observable.from(urls)) .map(url1 -> textView.getText() + url1 + "\n\n") .subscribe(url1 -> { textView.setText(url1); }); 

or even easier:
 queryURLs(url) .flatMap(Observable::from) .map(url1 -> textView.getText() + url1 + "\n\n") .subscribe(textView::setText); 

Let's compare the construction above with the resulting code and feel the power and simplicity of lambda expressions.

7. Increase capacity


In the next step, we complicate our processing and use the flatMap () operator to connect the second prepared queryTitle () method, which also returns an observer. This method returns the Title of the site by reference to the site. Let's create an example in which we will form and display a list of site headers by the links found on the web page, i.e. instead of the resulting list of links to sites in the previous example, we display the headers (Title) of these sites:
 public void example4(final TextView textView, String url) { queryURLs(url) .flatMap(new Func1<List<String>, Observable<String>>() { @Override public Observable<String> call(List<String> urls) { return Observable.from(urls); } }) .flatMap(new Func1<String, Observable<String>>() { @Override public Observable<String> call(String url) { return queryTitle(url); } }) .subscribe(new Action1<String>() { @Override public void call(String title) { textView.setText(title); } }); } 

or in abbreviated form:
 queryURLs(url) .flatMap(Observable::from) .flatMap(this::queryTitle) .subscribe(textView::setText); 

add map () to form a list of headers:
 queryURLs(url) .flatMap(Observable::from) .flatMap(this::queryTitle) .map(url1 -> textView.getText() + url1 + "\n\n") .subscribe(textView::setText); 

using the filter () operator, we filter out empty lines with the value null:
 queryURLs(url) .flatMap(Observable::from) .flatMap(this::queryTitle) .filter(title -> title != null) .map(url1 -> textView.getText() + url1 + "\n\n") .subscribe(textView::setText); 

using the take () operator, we take only the first 7 headers:
 queryURLs(url) .flatMap(Observable::from) .flatMap(this::queryTitle) .filter(title -> title != null) .take(7) .map(url1 -> textView.getText() + url1 + "\n\n") .subscribe(textView::setText); 

The last example showed that combining a set of methods plus using a large number of available operators plus lambda expressions and we get from several lines a powerful handler for various data streams.

All the examples given in the article are posted here .

Sources:


  1. Official documentation
  2. Grokay * RxJava, Part One: The Basics
  3. Getting Started With ReactiveX on Android
  4. RxJava - Tutorial
  5. Getting Started with RxJava and Android
  6. Reactive Programming with RxJava in Android
  7. Party tricks with RxJava, RxAndroid & Retrolambda

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


All Articles