はじめに
Androidアプリからインターネット上のAPIにHTTP経由でリクエストし、レンスポンスの結果を画面に表示したいということがあると思います。通信をすると結果が返ってくるまでにブロッキングしないようにしたい。レスポンスの結果をパースすることが面倒ということがあります。本稿では、海外の話題webページの要約を翻訳したEpitomeのタイトルを表示するアプリを作ってみたいと思います。
特徴としては通信部分をRxAndroidを使用して、リクエストし、レスポンスをパース後にUI操作の流れをわかりやすく記述する。リクエストについてはRetrofitによりRESTfulに記述し、Gsonによるレスポンスをパースした結果を定義することにより、面倒なパースを自動的にパースします。
UI操作はメインスレッド、通信は別スレッド
HTTP通信をメインスレッド(UIスレッド)で行うと、レスポンスが返ってくるまでにUI操作をすることができずに、画面操作をすることができません。なので、別のスレッドでバックグラウンドで行うことが望ましいです。
また、画面に結果を表示する処理はメインスレッドでなければ実行できないという、Androidの制約があります。メインスレッドではないスレッドで実行すると例外が発生します。
RxJavaとRxAndroid
RxJavaは要求と応答(1:1)、ストリーム(1:N)のデータを扱うライブラリです。要求と応答はたとえば、APIでの通信においてクライアントからサーバにリクエスト(要求)し、応答(レスポンス)をクライアントが受け取ります。ストリームは、要求に対して、時系列に複数のイベントが発生します。RxAndroidはRxJavaライブラリにAndroid用に便利な機能を追加したものです。
Epitomeのタイトルを表示するアプリ
それではEpitomeのタイトルを表示するアプリを作ってみましょう。完成したソースはGithubのRxAndroidSampleにあります。
このアプリは以下のように一覧でタイトルを表示します。
APIで通信した結果のレスポンスをパースし、ListViewに表示します。
RxAndroidによる通信は(1)リクエストをしてレンスポンスをパース、(2) 結果を受け取りUI操作をするに分かれます。(1)はsubscribeOn(Schedulers.io())により、io用のスレッドで行う設定、(2)はobserveOn(AndroidSchedulers.mainThread())によりメインスレッドで行う設定しています。
ViewObservable.bindView(listView, observable)は通知先のlistViewを設定しています。通知先のViewがなかった時にエラーにならないようにしています。
Observable observable = Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
// (1) リクエストをしてレンスポンスをパース
subscriber.onCompleted(); // 完了を通知する
}
})
.subscribeOn(Schedulers.io()); // io用のスレッドで行う設定
ViewObservable.bindView(listView, observable);
observable
.observeOn(AndroidSchedulers.mainThread()) // メインスレッドで行う設定
.subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
// (2) 結果を受け取りUI操作をする
}
});
}
(1)部分の実際のコードを以下に示します。
Observable observable = Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
EmpitomeBeamService empitomeBeamService = ServiceGenerator.createService(
EmpitomeBeamService.class, EmpitomeBeamService.ENDPOINT); // (3)
EpitomeBeam epitomeBeam = empitomeBeamService.getBeam(); // (4)
for (EpitomeEntry epitomeEntry : epitomeBeam.sources) {
titles.add(epitomeEntry.title);
}
subscriber.onCompleted();
}
})
(3)のEmpitomeBeamServiceは以下のように定義されています。
public interface EmpitomeBeamService {
public static final String ENDPOINT = "https://ja.epitomeup.com";
@GET("/feed/beam")
EpitomeBeam getBeam();
}
(4)はRetrofitとOkHttpライブラリによりempitomeBeamService.getBeam()は以下にアクセスします。
https://ja.epitomeup.com/feed/beam
試しにブラウザから上記のURLにアクセスするとJSONで記事情報が取得できます。
{"sources":[{"id":"176271","scheme":"gists","title":"Samsung と IBM、ビットコインの技術を仮想通貨以外の分野に応用へ","views":347,"epitome_url":"https://ja.epitomeup.com/sources/176271","upstream_url":"http://www.bloomberg.com/news/articles/2015-04-10/samsung-plans-to-take-bitcoin-technology-beyond-virtual-currency","published_at":"2015-04-10T09:47:53.000Z","gists":[{"content":"シリコンバレーを拠点とする Samsung Research America で Bitcoin の応用技術が研究されている"},{"content":"Bitcoin の Blockchain を応用することで、より優れた真正保証手段を作れるのではと考えている"},{"content":"Blockchain は従来のストレージの代替手段としても活用できる"},{"content":"最近は企業の Bitcoin への関心や、Bitcoin 関連スタートアップへの関心が高まってきている"}]},{"id":"142503","scheme":"gists","title":"Facebook の DB エンジニアが語る、InnoDB が優位性を失った理
...省略
JSONの構造はsourcesの値が記事毎に配列で格納されています。
{"sources:
[{"id":"176271", "scheme":"gists", "title": "title":"Samsung と IBM、ビットコインの技術を仮想通貨以外の分野に応用へ", ..},
{"id":"142503","scheme":"gists","title":"Facebook の DB エンジニアが語る、InnoDB が優位性を失った理由", ...},
...
]
}
つまり、記事のクラスと記事を複数格納するListクラスを持ったクラスをGsonで作成すればよいことになります。
public class EpitomeBeam {
@SerializedName("sources")
public List<EpitomeEntry> sources;
}
public class EpitomeEntry {
final static String SCHEME_GISTS = "gists";
@SerializedName("id")
public String id;
@SerializedName("scheme")
public String scheme;
@SerializedName("title")
public String title;
//... 省略
この2つのクラスはAndroid-Heliumから使わせてもらっています。
(4)を実行すると戻り値がEpitomeBeamになりますのでJSONを自動的にパースしてクラスに格納されます。
epitomeBeam.sourcesからタイトルであるepitomeEntry.titleを取り出し、List型のtitlesに追加します。
for (EpitomeEntry epitomeEntry : epitomeBeam.sources) {
titles.add(epitomeEntry.title);
}
タイトルを設定後に以下で完了を通知します。
subscriber.onCompleted(); // 完了を通知する
subscriber.onCompleted()により(2)が呼ばれます。
(2)の部分でタイトル保持したtitlesをArrayAdapterにセットし、それをlistViewに設定し、画面一覧にタイトルがめでたく表示されます。
observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
// (2) 結果を受け取りUI操作をする
ArrayAdapter<String> adapter = new ArrayAdapter(finalMainActivity,
android.R.layout.simple_expandable_list_item_1, titles);
listView.setAdapter(adapter);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer progress) {
}
});
おわりに
通信部分をRxAndroidを使用して、リクエストし、レスポンスをパース後にUI操作の流れをわかりやすく記述する。リクエストについてはRetfitによりRESTfulに記述し、Gsonによるレスポンスをパースした結果を定義することにより、面倒なパースを自動的にパースしました。
今回は要求と応答(1:1)のパターンだったため、onCompleted()のみでしたが、ストリーム(1:N)のonNext()をする方法もあります。