Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
187
Help us understand the problem. What is going on with this article?
@rejasupotaro

RxJavaでAPIクライアントを作る

More than 5 years have passed since last update.

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();
}

ページネーション

リストの一番下までスクロールしたときに次のページをリクエストするときには以下のようにします。

  1. リクエスト時にBehaviorSubjectを作る
  2. レスポンスのnextのObservableを保持しておく
  3. MoreLoadScrollListenerなどで一番下に達したらsubjectにnextを渡す

もしかしたらもっと良いやり方があるのかも。

所感

FRPがにわかに盛り上がって来ていますが、RxJava/RxAndroidに関しては実際どうやって使うのかの知見があまりなかったので書いてみました。FRPと言いましたが僕はRxJavaをAndroidの非同期処理の手段として使いたかったというのがあり、RxAndroidはライフサイクル周り以外では使っていません。他の人が非同期処理をどうしているかとか、RxAndroidはどういうふうに使うと効果的みたいな話を聞いてみたい。

187
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
187
Help us understand the problem. What is going on with this article?