RxAndroidを使って良かった2つのこと

  • 70
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

What's RxAndroid?

RxAndroidとは、ReactiveXのAndroid版で、いわゆるオブザーバーパターンを簡単に実装できるものです。
すでに記事は数多く出回っていますが、実際に自分が使ってみて感じた二つの利点を書いておきます。

  • 非同期処理が楽
  • 複数個所で最新の情報を表示

導入はbuild.gradleに書けばできます。

build.gradle
compile 'io.reactivex:rxjava:1.1.5'
compile 'io.reactivex:rxandroid:1.2.0'

非同期処理が楽

AsyncTaskの場合

大抵のアプリでは通信をすると思いますが、この通信処理をまじめに書こうとすると、Androidの場合AsyncTaskなどを使って書きますが、大変煩雑なプログラムになりがちです。

new AsyncTask<Params, Progress, Result>() {
    @Override
    protected Result doInBackground(Params... params) {
        // 非同期処理
        return new Result();
    }

    @Override
    protected void onPostExecute(Result result) {
        // 終わった後の処理
    }
}.execute();

なぜ煩雑になりやすいかというと、

  • 非同期処理と終わった後の処理を近くに書かなければならない
  • 終わった後の処理に参照が残ったままになる(=NullPointerExceptionで落ちやすくなる)

ことなどが挙げられると思います。

RxAndroidの場合

これをReactiveXを使って書くと、

Single.create(new Single.OnSubscribe<Result>() {
    @Override
    public void call(SingleSubscriber<? super Result> singleSubscriber) {
        // 非同期処理
        singleSubscriber.onSuccess(new Result());
    }
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Result>() {
    @Override
    public void call(Result result) {
        // 終わった後の処理
    }
});

一見AsyncTaskの場合と同じように見えますが、このように近くに処理を書くことは稀で、多くの場合はModelとActivityに分けることでその真価を発揮します。

非同期処理(Model)

public Single<Result> request(Params... params) {
    return Single.create(new Single.OnSubscribe<Result>() {
        @Override
        public void call(SingleSubscriber<? super Result> singleSubscriber) {
            // 非同期処理
            singleSubscriber.onSuccess(new Result());
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

終わった後の処理(Activity)

request().subscribe(new Action1<Result>() {
    @Override
    public void call(Result result) {
        // 終わった後の処理
    }
});

ラムダ式

Retrolambdaを使うか、最近だとJack (Java Android Compiler Kit)を使うことで、Java8のラムダ式を使うことができます。
RxAndroidを使う場合は、ほぼ必須といえるでしょう。

ラムダ式を使って書き直した場合、

非同期処理(Model)

public Single<Result> request(Params... params) {
    return Single.create((Single.OnSubscribe<Result>) singleSubscriber -> {
        // 非同期処理
        singleSubscriber.onSuccess(new Result());
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

終わった後の処理(Activity)

request().subscribe(result -> {
    // 終わった後の処理
});

例外処理

通信処理の場合、途中でネットワークが切れるなど、例外処理は不可欠です。それも簡単に書くことができます。

非同期処理(Model)

public Single<Result> request(Params... params) {
    return Single.create((Single.OnSubscribe<Result>) singleSubscriber -> {
        // 非同期処理
        try {
            singleSubscriber.onSuccess(new Result());
        } catch (Exception e) {
            singleSubscriber.onError(e);
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

終わった後の処理(Activity)

request().subscribe(result -> {
    // 終わった後の処理
}, throwable -> {
    // 例外処理
});

こうすることでかなりきれいに通信処理を書くことができます。

NPE回避

AsyncTaskでよく発生するNullPointerExceptionも、RxAndroidを使うと比較的容易に回避することができます。

上述した、終わった後の処理で、subscribeすると、subscriptionという戻り値が返ります。

Subscription subscription = request().subscribe();

RxAndroidにはCompositeSubscriptionというList<Subscription>みたいなクラスがあるので、それにaddしておき、onDestroyなどでclearすれば、この後でsubscribeの処理が走らなくなるので、NPEを防ぐことができます。

CompositeSubscription compositeSubscription = new CompositeSubscription();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    compositeSubscription.add(request().subscribe());
}

@Override
protected void onDestroy() {
    compositeSubscription.clear();
    super.onDestroy();
}

(ただし、ダイアログ系の場合、onStop ~ onDestroyの間で実行するとエラーになる場合があるので、その場合は注意が必要です)

複数個所で最新の情報を表示

RxAndroidのもう一つの良かった点は、複数個所で最新の情報を表示することができることです。
Observerパターンを実現するものであることを考えると、こちらがメインの利点と言ってもいいかもしれません。

具体的なユースケース以下のようなものです。

layout-2016-05-11-104645_.png layout-2016-05-11-104726_.png layout-2016-05-11-104738_.png layout-2016-05-11-104700_.png

リストから要素をタップし詳細画面を見た後、お気に入りボタンを押して、バックキーでリストに戻った時に、お気に入りボタンを押したことが反映されているという感じです。

Androidの場合バックキーで前の画面に戻りますが、ここで情報が古かったり、毎回通信したりしていると、「webっぽい」と言われてしまうわけです。

これをAndroid実現しようとすると、直接関数を呼び出したり、broadcastを使ったりなど、煩雑になります。
RxAndroidでは、BehaviorSubjectを使うことで、簡単に実装できます。

BehaviorSubject

BehaviorSubjectはザックリ言うと、変数の強化版です。

通常、変数は、代入(set)と取り出し(get)ができ、関数を用意する場合は以下のようになると思います。

// set
student.setName("foo");

// get
String name = student.getName();

しかし、この場合だと、nameはその後にstudentsetNameを呼んでも変更されません。

student.setName("foo");
String name = student.getName();
student.setName("bar");
Log.d("name", name); // fooのまま!

Androidの場合、表示面はDataBindingを使うこともできますが、RxAndroidでも実現することができます。

BehaviorSubject<String> studentName = BehaviorSubject.create();

// set
studentName.onNext("foo");

// get(即時)
String name = studentName.getValue();

// get(Observableとして)
Observable<String> observable = studentName.asObservable();

BehaviorSubjectの場合、変数のように値を取り出すgetValueのほかに、Observableとして取り出すことができます。
取り出したObservableは以下のように使います。

observable.subscribe(name -> {
    Log.d("name", name);
});

こうすることで、set関数であるonNextを呼ぶたびに、上記の処理が走り、表示面の更新などを行うことができます。

注意点

状態(データ)を全てモデルに置く

データを全てモデルに置き、Activityの表示は常にモデルに従うようにしておかないと、現在の表示が何に由来しているか分らなくなってしまいます。
また、いろいろな場所にBehaviorSubjectを置き、値の更新があったら別の値も書き換える、とやっていると、無限ループになりかねないので注意が必要です。

更新を受けた後の処理はなるべく軽く

値の更新があったら走る処理は、結構頻繁に呼ばれることになると思うので、表示面の更新などにとどめておいたほうが無難です。
通信処理をあると裏で無駄な通信をしてしまうので避けたほうがいいと思います。
またDialogやToastなどを出す処理を書いてしまうとActivityが前面にいないのに、DialogやToastだけ表示されるという奇妙な状態になるので、避けたほうが無難だと思います。

おわりに

ReactiveXに関する解説記事や、様々な関数の使い方を紹介した記事はすでにたくさんありますが、その中でも自分が特に恩恵を授かった2つの事柄に絞って書いてみました。
何か間違いなどありましたら知らせていただけるとありがたいです :bow: