RxJavaのモチベーション
HTTPクライアントは今ならOkHttp一択なのですが、APIクライアントには非同期に通信をおこなってほしいものですが、非同期処理をおこなうAndroidフレームワークのAsyncTaskやAsyncTaskLoaderは正直使いやすいとは言えません。Volleyは設計は綺麗で拡張もしやすかったのですが、Googleとしての立ち位置がよく分からなかったので OkHttp + 非同期処理を担う何か
を探していました。
それでPromiseライクなBoltsと迷ったのですが、個人的な好みでRxJavaを採用してみました。
APIクライアントの設計
以前書いたもの の参考実装としてライブラリを書いてみました。
リソースへのアクセスの仕方は以下のようになっています。
// GET /users/rejasupotaro
GitHub.client().user("rejasupotaro")
.map(Response::entity)
.subscribe(user -> ...);
// You can access myself if you have authenticated
GitHub.client().user()
.map(Response::entity)
.subscribe(user -> ...);
// GET /search/repositories?q=Android&sort=stars&order=desc&page=1&per_page=20
GitHub.client().searchRepositories("Android", Sort.STARS, Order.DESC, 1, 20)
.map(Response::entity)
.subscribe(repositories -> ...);
// You can find the hottest repositories created in the last week
GitHub.client().hottestRepositories()
.map(Response::entity)
.subscribe(repositories -> ...);
リソースへのアクセス以外ではページネーションができたりAuthorizationをセットしたりCache-Controlをセットしたりすることができます。
APIを追加するには以下のようにPathとリソースのクラスを指定するみたいな感じです。
public Observable<Response<User>> user(String username) {
String path = String.format("/users/%s", username);
return request(Method.GET, path).to(new TypeToken<User>() {
});
}
今回はGitHubのAPIクライアントを作ってみましたが、REST APIであればメタデータをどこに含めるかなどが違うくらいで基本的に同じ構造で作れそうです。
Retrofitを使えば同じようなことができますが、個人的にはヘッダを含めレスポンスの形式はAPIの設計に依存するところが大きいので、個々で作った方が柔軟性高いんじゃないかなと思いますが、ケースバイケースです。
RxAndroidを使ってみる
これだけだとよくあるRxJava/RxAndroidの紹介記事ですが、どうやってbindするとか、どうやってunsubscribeするとか、どうやってページネーションするというのはあまり見たことがなかったので、RecyclerViewでリスト表示して下までスクロールしたら次のページを読む、のような一般的な サンプル を書いてみました。
bind
AppObservable.bindActivity
, AppObservable.bindFragment
, ViewObservable.bindView
の3つがよく使われると思います。
bindActivityとbindFragmentは内部でOperatorConditionalBindingが使われていて、これはbindが外れたどうかみていて、外れていたら通知しないというもので、bindActivityならActivityが終了していないかとか、FragmentならaddされていてActivityが終了していないかのような条件が渡されています。これによって安全にsubscribeが呼び出されるようになります。しかし、unsubscribeはする必要があり、適切にunsubscribeしないとメモリリークする恐れがあります。
bindViewはwindowからdetachされるのを監視していて、自動でunsubscribeもおこなってくれます。
unsubscribe
ActivityならonDestroy、FragmentならonDestoryView、ViewならbindViewでメモリリークしないようにしてあげます。subscribeする前にonDestroyに達してunsubscribeしようとしたときにNPEになるのを防ぐために、空のsubscribeをセットすると良いです。
// This is field
private Subscription subscription = Subscriptions.empty();
また、subscriptionが複数ある場合には CompositeSubscription
を使うと、安全に複数のsubscriptionをunsubscribeすることができます。
private CompositeSubscription subscriptions = new CompositeSubscription();
...
subscriptions.add(...)
subscriptions.add(...)
subscriptions.add(...)
...
@Override
public void onDestroy() {
subscriptions.unsubscribe();
super.onDestroy();
}
ページネーション
リストの一番下までスクロールしたときに次のページをリクエストするときには以下のようにします。
- リクエスト時にBehaviorSubjectを作る
- レスポンスのnextのObservableを保持しておく
- MoreLoadScrollListenerなどで一番下に達したらsubjectにnextを渡す
もしかしたらもっと良いやり方があるのかも。
所感
FRPがにわかに盛り上がって来ていますが、RxJava/RxAndroidに関しては実際どうやって使うのかの知見があまりなかったので書いてみました。FRPと言いましたが僕はRxJavaをAndroidの非同期処理の手段として使いたかったというのがあり、RxAndroidはライフサイクル周り以外では使っていません。他の人が非同期処理をどうしているかとか、RxAndroidはどういうふうに使うと効果的みたいな話を聞いてみたい。