pretty

Friday 25 July 2014

RxJava for UI events on Android example

In the paper Deprecating the Observer Pattern, part 3 "Reactors: composable observers without inversion of control" there is an example of the reactor that processes mouse input events, written in Scala using scala-react library. In this post I'll describe how to create composable observers for touch events for Android with RxJava library.

The tools used are the RxJava library itself and retrolambda plugin to employ lambda functions on Android. Here is the sample build.gradle file with these two dependencies. To bridge from Android event listeners to RxJava Observers the Subject is created, it will listen to touch events and reemit them to RxJava Observers.
private final PublishSubject mTouchSubject = PublishSubject.create();
 
public MouseDragView(Context context, AttributeSet attr) {
        super(context, attr);
        
        setOnTouchListener((View v, MotionEvent event) -> {
            mTouchSubject.onNext(event);
                return true;
        });
}


Then the Observables are created by filtering the Subject Observable and each of them exposes corresponding event stream to touch ups, moves and touch downs. While each of these three new observables will receive all the events from the Subject Observable, they will call their subcribers' onNext() function only for the events that suit the filter predicate.

private final Observable mTouches = mTouchSubject.asObservable();
    private final Observable mDownObservable = 
               mTouches.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_DOWN);
    private final Observable mUpObservable =
               mTouches.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_UP);
    private final Observable mMovesObservable =
               mTouches.filter(ev -> ev.getActionMasked() == MotionEvent.ACTION_MOVE);


Path should be created with the touch down event. So the new Observer is spawned using the subcribe (final Action1 onNext) function of Observable. The lambda function that is passed as an argument will be the newly created Observer's onNext() handler.

    mDownObservable.subscribe(downEvent -> {
           final Path path = new Path();
           path.moveTo(downEvent.getX(), downEvent.getY());
           Log.i(downEvent.toString(), "Touch down");
    });


After the path is created and has the starting point, lets watch for the move events.

 mDownObservable.subscribe(downEvent -> {
            final Path path = new Path();
            path.moveTo(downEvent.getX(), downEvent.getY());
            Log.i(downEvent.toString(), "Touch down");

            mMovesObservable
                    .subscribe(motionEvent -> {
                        path.lineTo(motionEvent.getX(), motionEvent.getY());
                        draw(path);
                        Log.i(motionEvent.toString(), "Touch move");
            });
        });


For each touch down event the new Observer for move events is created. Now we need to also create an Observer for touch up event. On touch up we'll unsubscribe from move events, and close the path. It's important to unsubscribe because otherwise for each next touch down we will be creating yet another moves Observer while leaving behind all the moves Observers created for previous touch downs. The unsubscription is achieved via takeUntil() operator. The RxJava wiki states that takeUntil() "emits the items from the source Observable until another Observable emits an item or issues a notification. This means that our Observer will be automatically unsubscribed from moves Observable when touch up Observable emits an item. Hence the moves Observable will become "cold" Observable, as it doesn't have Observers, and will stop emitting the items.

    mDownObservable.subscribe(downEvent -> {
            final Path path = new Path();
            path.moveTo(downEvent.getX(), downEvent.getY());
            Log.i(downEvent.toString(), "Touch down");

            mMovesObservable
                     .takeUntil(mUpObservable
                             .doOnNext(upEvent -> {
                                 draw(path);
                                 path.close();
                                 Log.i(upEvent.toString(), "Touch up");
                             }))
                    .subscribe(motionEvent -> {
                        path.lineTo(motionEvent.getX(), motionEvent.getY());
                        draw(path);
                        Log.i(motionEvent.toString(), "Touch move");
                    });

        });


The full example project for Android Studio is at github



No comments :

Post a Comment