神刀安全网

Reactive Android UI Programming with RxBinding

There’s an old saying that rings true in software…

“The only thing that is constant is change.”

The same can easily be said of Android. For example, how many times have you found yourself implementing a click listener, a text change listener ,or some other mundane callback that has a different signature? Android Studio alleviates us from having to memorize the callbacks, listeners, and their signatures, but it unfortunately does not provide any uniformity. After a while, your code seems to resemble a mess of anonymous classes that are storing state in a field in the fragment or activity. This is further complicated if you need to achieve a more reactive architecture where inputs of one widget/view are based off the outputs of another view (or its actions). For most devs, implementing this kind of reactive callback chaining yourself will prove a time-consuming and error-prone mess. Thankfully, the easy-to-use RxBinding libraries can help.

What are the RxBinding Libraries?

RxBinding is a set of libraries that allow you to react to user interface events via the RxJava paradigm. Let’s take a look at a few examples. This is how Android developers typically react to a button click event:

Button b = (Button)findViewById(R.id.button); b.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {               // do some work here                  }         });

Using RxBinding , you can accomplish the same thing but with RxJava subscription:

Button b = (Button)findViewById(R.id.button); Subscription buttonSub =                 RxView.clicks(b).subscribe(new Action1<Void>() {                     @Override                     public void call(Void aVoid) {                         // do some work here                     }                 }); // make sure to unsubscribe the subscription

Let’s take a look at another example, this time with a text change listener for an EditText:

final EditText name = (EditText) v.findViewById(R.id.name); name.addTextChangedListener(new TextWatcher() {     @Override     public void beforeTextChanged(CharSequence s, int start, int count, int after) {              }      @Override     public void onTextChanged(CharSequence s, int start, int before, int count) {         // do some work here with the updated text     }      @Override     public void afterTextChanged(Editable s) {      } });

Now the same thing, but written with RxBinding support:

final EditText name = (EditText) v.findViewById(R.id.name); Subscription editTextSub =     RxTextView.textChanges(name)             .subscribe(new Action1<String>() {                 @Override                 public void call(String value) {                     // do some work with the updated text                 }             }); // Make sure to unsubscribe the subscription

While this may seem like trading an apple for an orange, it actually gives you something very different: consistency. These are merely two examples of countless listeners and callbacks that are available via Android’s various views and widgets that show how they differ in the traditional sense of an Android implementation vs the RxBinding implementation. When using RxBinding, you have the same consistent implementation: an RxJava subscription. This offers less cognitive load as well as all of the other benefits of RxJava.

More Granular Control

In the example above, I use the RxTextView.textChanges() method to react only to the text change event. In traditional Android, we have to implement a full TextWatcher to accomplish this, wasting multiple lines of code because the beforeTextChanged event and afterTextChanged callbacks must also be implemented. This dead code simply pollutes the class and offers no additional value. With RxBinding I can achieve more granular control over what I want to react to in my application without the additional overhead in my codebase.

It is important to note that in this case RxBinding is simply implementing a TextWatcher for you and only calling the onTextChanged event for you. Here’s the implementation of the TextViewTextOnSubscribe class that RxBinding uses behind the scenes for you in the RxTextView.textChanges() observable:

final class TextViewTextOnSubscribe implements Observable.OnSubscribe<CharSequence> {   final TextView view;    TextViewTextOnSubscribe(TextView view) {     this.view = view;   }    @Override public void call(final Subscriber<? super CharSequence> subscriber) {     checkUiThread();      final TextWatcher watcher = new TextWatcher() {       @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {       }        @Override public void onTextChanged(CharSequence s, int start, int before, int count) {         if (!subscriber.isUnsubscribed()) {           subscriber.onNext(s);         }       }        @Override public void afterTextChanged(Editable s) {       }     };     view.addTextChangedListener(watcher);      subscriber.add(new MainThreadSubscription() {       @Override protected void onUnsubscribe() {         view.removeTextChangedListener(watcher);       }     });      // Emit initial value.     subscriber.onNext(view.getText());   } }

The real benefit here is the syntactic sugar over the existing Android API’s that make your code more readable and consistent. Regardless if you’re observing a click event, a text change event, or even a dismissal of a Snackbar, RxBinding provides a consistent implementation that you can use to react accordingly.

Transforming with Operators

Since RxBinding is applying RxJava paradigms to existing Android Views and Widgets, you can use RxJava operators to perform transformations on the stream of data that is emitted in real time. Let’s take a look at an example:

Assume you have an EditText field that you want to watch for changes (as the user types, etc). When the text changes, you want to take the text string, reverse it, and output it to the screen in a different TextView. Here’s how you could do that:

final TextView nameLabel = (TextView) findViewById(R.id.name_label);  final EditText name = (EditText) findViewById(R.id.name); Subscription editTextSub =     RxTextView.textChanges(name)             .map(new Func1<CharSequence, String>() {                 @Override                 public String call(CharSequence charSequence) {                     return new StringBuilder(charSequence).reverse().toString();                 }             })             .subscribe(new Action1<String>() {                 @Override                 public void call(String value) {                     nameLabel.setText(value);                 }             });

In the example above, the EditText text changed event is observed via the RxTextView.textChanges() observable, which is in turn mapped to a string via the map() operator. The map() operator returns a reversed string and then the subscription sets the nameLabel to that text value. As you can imagine, you can do a number of things with built in RxJava operators and any custom operators that you’ve built thus far in your application.

I’d like to mention again the power of the syntactic sugar over the Android views and widgets API. By conforming to the same RxJava Observable paradigm, you can chain operations together that would normally not be able to. This has great power as you start to compose your application in a reactive nature.

RxBinding Offers More

There have been a few occasions where I needed to support multiple click listeners on a view (for various reasons). As you probably know, this is not possible in Android unless you write some custom code to handle it. Supporting multiple listeners with RxBinding is very simple. It’s important to note that RxBinding does not enable this, but it is enabled through RxJava operators like publish() , share() , and replay() . It’s up to you to decide which one suits your needs. In this example, I’m going to use the share() operator to enable multicast listener support (multiple click listeners):

Button b = (Button) v.findViewById(R.id.do_magic); Observable<Void> clickObservable = RxView.clicks(b).share();  Subscription buttonSub =         clickObservable.subscribe(new Action1<Void>() {             @Override             public void call(Void aVoid) {                 // button was clicked.              }         }); compositeSubscription.add(buttonSub);  Subscription loggingSub =         clickObservable.subscribe(new Action1<Void>() {             @Override             public void call(Void aVoid) {                 // Button was clicked             }         }); compositeSubscription.add(loggingSub);

If you were to remove the .share() call from the above code, only the last subscription would get called. As the share() operator docs state :

“Returns a new Observable that multicasts (shares) the original.”

Using share in this context allows multiple click listeners to be applied to one button, which is very powerful.

RxBinding Idioms and Installation

There are a few things to be aware of when working with RxBinding.

First, weak references should not be used – as per the docs:

“Weak references should not be used. RxJava’s subscription graph allows for proper garbage collections of reference-holding objects provided the caller unsubscribes.”

Secondly, in various parts of the Android framework ,the UI events emit multiple values instead of a single argument like a click listener (where view is the only argument). RxJava observables only emit a single object, not an array of values. Therefore, a wrapper object is needed to combine these values into a single object in these instances. For example, the scroll change listener returns multiple values such as scrollX , scrollY , oldScrollX , oldScrollY . In RxBinding these values are combined into a wrapper object called ViewScrollChangeEvent ( source ). When the RxView.scrollChangeEvents() observable is subscribed to, the ViewScrollChangeEvent will be the value emitted from the onNext method ( source ). Therefore, you will get the ViewScrollChangeEvent which contains the values you’re interested in.

Third, each library is broken down based upon where it is in the Android platform. For example, views and widgets, which are in the android.widget.* package, will be found in the com.jakewharton.rxbinding.widget.* package.

RxBinding is not limited to the platform classes either. There are RxBinding libraries for the support libraries as well. For example the basic RxBinding support for platform classes is found by using this depdendency:

compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'

Let’s assume that you are using the design support library and you want the RxBinding goodness there as well. You can also include this dependency to get those bits as well:

compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0'

Furthermore, let’s assume that you’re now usingKotlin on Android, and you would like RxBindings for Kotlin as well. Simply tack on -kotlin to any of the dependencies to get the Kotlin version. e.g. –

compile 'com.jakewharton.rxbinding:rxbinding-kotlin:0.4.0'

Expand or Introduce Your RxJava Toolbox

If you haven’t yet embarked on your RxJava journey, RxBinding may be the gateway that hooks you. If you’re already hooked on RxJava, this is a great supplement to the regular classes. RxBinding simple to use, provides a consistent API for consumption, and makes your application much more composable and reactive.

Happy programming!

Download the source

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Reactive Android UI Programming with RxBinding

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮