みんからきりまで

きりみんです。

RxAndroidをカジュアルに使ってみるとか

RxAndroidは、RxJavaを内包しAndroidで利用するための機能を追加したものです。

RxJavaの概要と基本的な使い方については過去の記事で紹介しています。
過去記事内で「実際のAndroidアプリでのユーケースに合わせた例も書いてみたいと思います」とか書いてそのまま3ヶ月以上放置してしまいました。

RxJavaは非常に多機能かつFRPという新しい概念に基いて設計されており、使いこなすには相当に学習コストが掛かるため、利用するにはハードルが高いイメージなのではないでしょうか。
しかし、そこまでRxJavaを深く理解し様々な関数を活用しなくても、RxAndroidは結構気軽に利用出来るのではないかなという気がしてきたので、RxAndroidの活用ケースを考えてみようと思います。

AsyncTaskなどの代わりに非同期コールバック処理に利用する

RxAndroidを使えば、非同期処理を簡単に記述する事が出来ます。
onCompletedやonErrorなどの機構が用意されているので扱いやすいです。

Observable
        .create(new Observable.OnSubscribe<Integer>() {
            @Override
            public void call(Subscriber<? super Integer> subscriber) {
                for (int i = 0; i < 100; i++) {
                    // 重い処理
                    subscriber.onNext(i);
                }
                subscriber.onCompleted();
            }
        })
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<Integer>() {
            @Override
            public void onCompleted() {
                // 処理完了コールバック
            }

            @Override
            public void onError(Throwable e) {
                // 処理内で例外が発生すると自動的にonErrorが呼ばれる
            }

            @Override
            public void onNext(Integer progress) {
            }
        });

ポイントはsubscribeOnメソッドとobserveOnメソッドで、それぞれ処理自体とコールバックをどのスレッドで行うか指定する事が出来ます。
つまり、ビジネスロジックの返り値をObservableでラップする事で、利用する側が簡単に同期・非同期を切り替える事が出来るのです。

API通信の共通インターフェイスとして利用する

上の例の応用として、API通信の処理をObservableでラップすることで、APIの結果を共通のインターフェイスで簡単に扱えるようにしてみます。

まずはAPI通信処理を定義します。(例なので細かい部分は省略しますが、文字列配列が返ってくるようなAPIのイメージです)

class SampleApi {

    public static Observable<String> request() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                List<String> results = xxxx; // 通信処理
                for(String s : results){
                    subscriber.onNext(s);
                }
                subscriber.onCompleted();
            }
        });
    }
}

APIを利用する側は、上で定義したメソッドを単純に呼び出すだけで簡単に非同期コールバック処理を行えます。

SampleApi.request()
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<String>() {
            @Override
            public void onCompleted() {
            }

            @Override
            public void onError(Throwable e) {
            }

            @Override
            public void onNext(String s) {
            }
        });

また、Observableにラップしているので、通信とコールバックの間に様々な関数処理を挟む事が出来てとても便利です。 下の例は「空文字の結果を省き、全て大文字に変換し、最初の10件だけ」をonNextに渡すように処理を追加しています。

SampleApi.request()
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .filter(new Func1<String, Boolean>() {
            @Override
            public Boolean call(String s) {
                return !s.isEmpty();
            }
        })
        .map(new Func1<String, String>() {
            @Override
            public String call(String s) {
                return s.toUpperCase();
            }
        })
        .take(10)
        .subscribe(new Observer<String>() {
            @Override
            public void onCompleted() {
            }

            @Override
            public void onError(Throwable e) {
            }

            @Override
            public void onNext(String s) {
            }
        });

他にもObservableを合成したりもできるので、複数APIが絡むような処理も簡潔に書く事が出来そうです。

Viewのイベントを受け取る

RxAndroidにはViewのイベントを受け取るためのObservableを生成するクラスが用意されています。
クリックイベントを取得するためのViewObservable.clicksの他に、TextViewの変更を取得するWidgetObservable.textやListViewのonScrollを取得するlistScrollEventsなどがあります。

WidgetObservable.listScrollEvents(mListView)
        .subscribe(new Action1<OnListViewScrollEvent>() {
            @Override
            public void call(OnListViewScrollEvent onListViewScrollEvent) {
                
            }
        });

これらのイベントも様々なフィルター処理を噛ませたり複数のイベントを組み合わせたりする事が出来るため、複雑な要件に対して柔軟に対応出来る気がします。
Viewのイベントの他にSheredPreferenceやCursorのイベントを通知させるためのメソッドも用意されているようです。

コールバックの解除を一元管理する

ところで非同期コールバック処理を行っていると、結果が帰ってきた時には画面が終了していてクラッシュするといったバグをよくやらかします。
RxJavaではSubscription(subscribeメソッドを呼んだ時の戻り値)に対してunsubscribeメソッドを呼ぶ事でコールバックの解除を行う事が出来ます。
更にCompositeSubscriptionというクラスに複数のSubscriptionを溜めておき一気に解除する事が出来るため、以下のように画面が破棄されるタイミングでunsubscribeを行う事で、安心してコールバックを受けられます。

public class MainFragment extends Fragment {

    private CompositeSubscription subscriptions = new CompositeSubscription();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);
        subscriptions.add(SampleApi.request()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<String>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(String s) {
                    }
                }));
        subscriptions.add(Observable.from(new String[]{"a", "b", "c"})
                .subscribe(new Action1<String>() {
                    @Override
                    public void call(String s) {
                    }
                }));
        return rootView;
    }

    @Override
    public void onDestroyView() {
        subscriptions.unsubscribe();
        super.onDestroyView();
    }
}

どうやらちゃんとunsubscribeしないとメモリリークもするとのことなので、とにかく全部unsubscribeした方がいいようです。
※参考

流行るかな?

Mitsumineの開発にRxAndroidを導入してみて、あまり使いこなせてなくても普通に便利だったので紹介してみました。
RxJavaは最近ジワジワ話題になるようになってきた気もしますが、まだまだ一部ウケで一般的にはあまり知られていないんじゃないかなという気がします。
個人的には便利なので個人開発では積極的に使っていきたいですが、やはり学習コストが高めなのと、RxAndroidはまだバージョンが0.24でカジュアルにメソッドの移動や削除が行われているような状態なため、業務プロジェクトに導入するのは難しいかもしれません。
あと、やっぱりAndroidの場合ラムダがないのが致命的に辛い。最近はもう観念してGroovyを使うしかないんじゃないかという気がしてきている。
mapやfilterに渡すFuncをメソッドの戻り値にしたりstatic変数で定義したりすれば呼び出す側の可読性は上がるけれどなんか本末転倒な気もしてどうなのだろうか。