RxJava Tour (2): Getting started with RxJava (1)

Keywords: Programming Android Gradle Junit

0. Preface

This is probably the most serious series of articles I have delayed since the first one Journey to RxJava (1): Fundamentals of RxJava Thought Almost a year apart from this one, I was ashamed to think of the series, and even the impulse was almost deleted several times.The thing is, I immediately put myself into the company's new project after writing my first article last year. The busiest phase of the project was in early 2017, when RxJava2 was published, which made me feel that this series of articles has lost its value to continue. On the other hand, for some reasons I was too lazy to understand RxJava2 at all.I recently took a break to look at the first article and it suddenly came to mind that RxJava1 and RxJava2 are essentially responsive programming, so it makes sense to continue this series.Next, I'll start with RxJava1, then introduce RxJava2 through the differences between RxJava1 and RxJava2.Therefore, it makes sense to introduce RxJava1 when RxJava2 has already appeared.

In this article, we will start with Responsive Programming, compare Responsive Programming with Object-Oriented Programming, and deepen the understanding of Responsive Programming so as to reduce the learning curve of RxJava.

1. Start

To get started, let's take a quick look at the use of RxJava and add dependencies to build.gradle in your module:

compile 'io.reactivex:rxjava:x.y.z'

Where x.y.z is replaced by the latest version, Click Here .As of this time, the latest version is 1.3.0, so the configuration is as follows:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])        
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'

    testCompile 'junit:junit:4.12'

    //RxJava2
    compile "io.reactivex.rxjava2:rxjava:2.1.2"
    //RxJava1
    compile 'io.reactivex:rxjava:1.3.0'
}

As we can see, both RxJava1 and RxJava2 are configured here.For a smooth transition from RxJava1 to RxJava2, different package names were used when writing RxJava2 so that there would be no dependency conflict even if both were placed in the dependency.Therefore, RxJava1 and RxJava2 can be used at the same time, just be careful to check if the package names are introduced correctly to avoid introducing RxJava2 classes where RxJava1 should be used.

2. What kind of experience is RP?

Let's be patient with a question before we get started.In the previous article, we carefully discussed what RP is (Reactive Programming, the same below), as well as the characteristics of responsive programming and process- and object-oriented programming.

Let's take a look at how the same requirement is implemented in RP and OOP (Object Oriented Programming, the same below). This comparison can help you better understand responsive programming and RxJava.

2.1 A simple question

Our question is very simple: there are two input boxes for numbers in Android, one for numbers a and the other for numbers b, and we calculate the result c, a+b=c.

2.2 Interface and Initialization


Interface

This interface is very simple, so the code for the layout file is not pasted out.

The section about the Activity is also not difficult, but it is necessary to post it to illustrate the relationship of each control:

//Input box a
editTextA = (EditText) findViewById(R.id.et_a);
//Input Box b
editTextB = (EditText) findViewById(R.id.et_b);
//Result c
textViewC = (TextView) findViewById(R.id.tv_c);
//Calculate button c
button = (Button) findViewById(R.id.btn_cal);

Next, we solve this simple problem by using OOP and RP respectively.

Solutions for 2.3OOP

Let's start with an object-oriented approach to this problem. It's easy to see that there are three member variables, a, b, and c, and two methods for calculating and returning results.We have defined a class called Plus with the following code:

public class Plus {
    private int a;
    private int b;
    private int c;//c No get or set method

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }
    //Addition
    public void sum(){
        c = a + b;
    }
    //Get results
    public int getResult(){
        return c;
    } 

}

We abstract this problem into a class by OOP, assign values to variables by set method, calculate by sum method, and get the results by getResult method.

Next let's consider how RP is resolved.

Solution for 2.4RP

In the first section, we introduced the core concept of responsive programming, which is programming thinking that is all data streams.So consider our simple question in terms of data streaming. In the formula a+b=c, A and B are both data streams, and C is the response to data streams a and B.That is to change C by listening for changes in data stream a and B.

Now that the problem is clear, we need two streams of data to represent a and b, then add up the latest values and display them at c.

The code in the following section covers a lot of RxJava content that hasn't been introduced yet, which we won't cover in detail here, but will be explained one by one later.

2.4.1 Data Source Creation

First, let's look at the creation of Data Flow A and Data Flow B:

final Observable<TextViewAfterTextChangeEvent> observableA = RxTextView.afterTextChangeEvents(editTextA).filter(new Predicate<TextViewAfterTextChangeEvent>() {
        @Override
        public boolean test(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) throws Exception {
            return null != textViewAfterTextChangeEvent.editable() && !TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString());
        }
    }); 
    
final Observable<TextViewAfterTextChangeEvent> observableB = RxTextView.afterTextChangeEvents(editTextB).filter(new Predicate<TextViewAfterTextChangeEvent>() {
        @Override
        public boolean test(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent) throws Exception {
            return null != textViewAfterTextChangeEvent.editable() && !TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString());
        }
    });

RxTextView, a class in the RxBinding library, is used to listen for TextView-related events.RxBinding is a responsive library for Android UI controls, which we will cover in a later article.The RxTextView.afterTextChangeEvents() we use here actually corresponds to the TextWatcher::afterTextChanged() method, which is equivalent to adding a TextWatcher using the editTextA.addTextChangedListener() method, and then listening to its afterTextChanged() method, using the data after each change as a newly generated item for our subsequent processing.

For a reference to RxBinding, configure the following dependencies in the dependencies block of module's build.gradle:

compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'

2.4.2 Subscriptions to streams (listening)

Now that we have defined the data stream, we can listen for changes in the data source.But here's another question: Who listens for data streams?Listening on two streams separately is not a good solution, which can break the responsive design of RP, so we need a new data stream to combine data stream a and data stream b to form a new data stream c, then subscribe to the data stream C and display the value of each change on the interface.

Let's look at how data stream c was created:

Observable.combineLatest(observableA, observableB, new BiFunction<TextViewAfterTextChangeEvent, TextViewAfterTextChangeEvent, String>() {
        @Override
        public String apply(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent, @NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent2) throws Exception {
            int a = TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent.editable().toString());
            int b = TextUtils.isEmpty(textViewAfterTextChangeEvent2.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent2.editable().toString());
            return String.valueOf(a + b);

        }
    });

Here, for the first time, we've used operators in RxJava, an important concept in RP that is often used for data flow transitions.If we compare a data stream to a production pipeline, the operator is some instrument on the pipeline that can convert, combine, and so on.

Here we briefly introduce a combineLatest used, which is described later.CombineLatest combines the last data from multiple streams (we have two here) and generates a new stream to continue downward.The illustration is as follows:


Let's take the last int for a and b and add them together.

2.4.3 Subscription

After getting the newly generated data stream c, we subscribe to the data stream C to show each change of C in the interface with the following code:

Observable.combineLatest(observableA, observableB, new BiFunction<TextViewAfterTextChangeEvent, TextViewAfterTextChangeEvent, String>() {
        @Override
        public String apply(@NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent, @NonNull TextViewAfterTextChangeEvent textViewAfterTextChangeEvent2) throws Exception {
            int a = TextUtils.isEmpty(textViewAfterTextChangeEvent.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent.editable().toString());
            int b = TextUtils.isEmpty(textViewAfterTextChangeEvent2.editable().toString())? 0 : Integer.valueOf(textViewAfterTextChangeEvent2.editable().toString());
            return String.valueOf(a + b);

        }
    }).subscribe(new Observer<String>() {
        @Override
        public void onSubscribe(@NonNull Disposable d) {
            Log.d(TAG, "onSubscribe");

        }

        @Override
        public void onNext(@NonNull String s) {
            Log.d(TAG, s);
            textViewC.setText(s);//Show result c
        }

        @Override
        public void onError(@NonNull Throwable e) {
            Log.e(TAG, e.getMessage());
        }

        @Override
        public void onComplete() {
            Log.d(TAG, "completed");
        }
    });

2.4.4 Effect

Notice that instead of using buttons for processing, we automatically respond to changes in the data.This fits the RP concept and is one of the advantages of RxJava.The final result is shown in the following figure:


3.Hello RxJava1!

Now that we've seen how easy it is to use RxJava for responsive programming, we can finally start learning RxJava1, starting with the simplest piece of code.

3.1 A simple piece of code

rx.Observable.just("Hello world")
            .subscribe(new rx.Observer<String>() {
                @Override
                public void onCompleted() {
                    Log.d(TAG,"completed.");
                }

                @Override
                public void onError(Throwable e) {
                    Log.e(TAG,e.getMessage());
                }

                @Override
                public void onNext(String s) {
                    Log.d(TAG,"next msg: " + s);
                }
            });

This is the simplest RxJava code, so let's get started.

3.2 Creation

The complete definition of the just method is as follows:

public static <T> Observable<T> just(final T value)

The just method is used to create an observer, which can convert any object into an observable object with the return type Observable<T>.

Details of creation will be discussed in the next article.

3.3 Subscription

Looking back at the first article, we said that RxJava is essentially an observer mode.There are observers and observers in the observer mode, and the observer subscribes to the observee, who responds when the observer changes.

RxJava differs from our common observer pattern in that the observer subscribes to the observer instead of the traditional observer.When Observable calls the subscribe method, its input is an Observer object, the observer.There are three methods in Observer, onNext, onError, and onComplete.

The observer calls onNext to process each item of data that passes through, or onError if the process fails, and the onComplete method should be called when all data is processed without errors.The process is described in the next section.

4. Summary

In fact, we still haven't really covered RxJava in this article, it's more like a supplement to the first one.But I think it's worth it because many people, including me, started learning RxJava by jumping directly at writing and the API, and the inadequate understanding of RP created a steeper learning curve.

In the next article, we'll go into more detail about creating and subscribing, as well as its source code.

Posted by lorenzo-s on Mon, 03 Jun 2019 19:32:11 -0700