さて前回の続きです。
前回までにリアクティブプログラミングとは何か、RxJavaはどのように記述するのか、などをほんと表面だけを書き連ねました。しかしRxandroid(特にhttp通信するだけであれば)あの程度の理解で十分だと思うので今回はもう実践に入りましょう!!
まだ一歩目を読んでいない人で基本を理解していない人はぜひ一歩目を!
http://qiita.com/Yuki_Yamada/items/a0855189988539c18b8f
まずはじめに
正直AsyncTaskLoaderでの記述ももう慣れてきてしまったのですがいかんせん前準備が結構必要だったりめんどくさい。記述も冗長だしネストも深くなりがちだし...流行ってるのにはわけがある、ということでさまざまなライブラリを駆使してもっと楽に非同期通信しよう。
では今回はどうするか。
- RxJava(Android)
- RetroFit
- Gson
を用いていきます!
RetroFit君は通信を担当しています。通信手、簡単に言えば武部沙織ですね(?)
Gson君はJsonのパースを担当しています。
RxJavaはList操作と非同期処理を担当しています。
今回はメインはコードになるのでGitにコードを挙げておきますのでそちらを見ながらこっちで軽い解説を見るのが一番わかりやすいかと思います!
SampleActivityのほうが今回の処理です。
https://github.com/SasakiYuki/RxJavaText
使用するAPI
金沢市のオープンデータから「イベント情報」のやつです。
金沢市イベント情報API
流れ
今回は↑のAPIからデータ全体を非同期(Retrofit + RxJava)で取得してパース(Gson)して「金沢市文化ホール」のデータだけにクエリしてlistViewに表示する内容です。
まずはEntityを用意する
の前にgradleに以下を
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.2.4'
金沢市イベント情報APIの構造に従い作ります。
最初のEntityはbase_urlとItemsのListです。
public class KanazawaEntity {
@SerializedName("items")
private List<KanazawaItems> kanazawaItemses;
@SerializedName("base_url")
private String base_url;
public List<KanazawaItems> getKanazawaItemses() {
return kanazawaItemses;
}
public String getBase_url() {
return base_url;
}
}
@SerializedNameについて
jsonのkeyと付けたい名前が別だった時に使います。(javaの予約語と被っていた場合もこれを用います。でないとエラーが出るので)
今回の場合だと特にbase_urlがsnake_caseなので人によっては
@SerializedName("base_url")
private String baseUrl;
等に変更する人もいるかもしれませんね。私はわかりやすいように今回はそのままsnake_caseで残してあります。
KanazawaItemsのほうも見ていきましょう。
public class KanazawaItems {
@SerializedName("group")
private String group;
@SerializedName("date_from")
private String data_from;
@SerializedName("date_to")
private String data_to;
@SerializedName("dates")
private List<String> dates;
@SerializedName("title")
private String titile;
@SerializedName("link")
private String link;
@SerializedName("description")
private String description;
@SerializedName("images")
private List<String> images;
public String getGroup() {
return group;
}
public String getTitile() {
return titile;
}
}
jsonの引数を持ってきてます。
今回もsnake_caseのものが存在しています。SerialzedNameを用いて変更してもよかったですがコードの見やすさ重視でそのまま残してあります。
通信部分
通信部分の実装をしていきます。
まずは下準備(まぁこれさえ終わればあとは呼び出すだけなんですが)
retrofitを呼び出すinterfaceを用意します。
public interface KanazawaApi {
/*@以降は用途に合わせて Get Post Put Delete を用いる
@Pathは{}内の物を照会
@Queryを用いることでよくあるevent.json?1d=19などを簡単に記述できる
例 getEvent(@Query("id"));
*/
@GET("/{name}.json")// 「/」から開始しないとはじかれる
public Observable<KanazawaEntity> getEvent(@Path("name")String url);
}
コメントアウトでいくつか説明を書いてあります。
今回はGETメソッドなのでGETを用います。
今回は@Queryを用いていませんので例を軽く示します。
例えば
http://www.hogehoge.jp/event.json?id=19
といったものにアクセスしたい場合
@GET("/event.json")
public Observable<KanazawaEntity> getEvent(@Query("id")int id);
といった感じで使用できます。
通信部分の実装は完成です。(簡単すぎる!!)
戻り値がObservable
retroFitでは戻り値にObservableを指定することができます(すごい!)
これのおかげでさっくりRxJavaと連携できるわけですね~
呼び出し
最後に呼び出してみましょう。
怒涛のRxJava祭りですが一歩目を読んでいれば理解できるはずです!!
まずは呼び出し部分を。
private void startApi (){
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(END_PONT)
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("=NETWORK"))
.build();
KanazawaApi api = adapter.create(KanazawaApi.class);
Observer observer = getEventObserver();
api.getEvent("event-all")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
GsonBuilderを呼び出します。
setFieldNamingPolicyでSnake_Caseで保持されているカラムを変更します。
private Observer<KanazawaEntity> getEventObserver(){
Observer observer = new Observer<KanazawaEntity>() {
@Override
public void onCompleted() {
Log.d(TAG,"completed");
}
@Override
public void onError(Throwable e) {
Log.d(TAG,"Error:"+e.toString());
}
@Override
public void onNext(KanazawaEntity kanazawaEntity) {
query(kanazawaEntity.getKanazawaItemses());
// List<KanazawaItems> itemse = kanazawaEntity.getKanazawaItemses();
// ArrayList<String> list = new ArrayList<String>();
// for (KanazawaItems item :itemse){
// if(item.getGroup().equals("金沢市アートホール"))
// list.add(item.getTitile());
// }
// setSimpleListView(list);
}
};
return observer;
}
コメントアウトしてある部分の処理でぶっちゃけListViewにタイトルを表示することはできるんですが...
せっかくRxJavaを勉強したのにlist操作をfor文で書いちゃうのはもったいないなぁと思ったので思い切ってlist操作(listviewへの表示)もRxJavaで記述しました!
コード全体で見たほうが早いと思うのでこのActivity全体を貼ります。
流れとしては以下の通りです。
startApiでKanazawaApiを呼びGsonを用いて取得したjsonをパースする。
それをgetEventObserverを用いて取得。
そのObserverの中のonNextの中でfor文でList表示してもいいのだが今回はRxJavaで処理するためにもqueryにlistを投げる。
queryの中でもう一回Observerを作成。
.filterは「金沢市文化ホール」以外で開催されるものをはじき、
.mapでitem全体ではなく開催されるイベントのタイトル(String)に変換
.subscribeで渡されたlistをlistviewに表示。
コード
public class SampleActivity extends AppCompatActivity {
private static final String END_PONT = "http://www.kanazawa-arts.or.jp";
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
startApi();
}
private void startApi (){
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(END_PONT)
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("=NETWORK"))
.build();
KanazawaApi api = adapter.create(KanazawaApi.class);
Observer observer = getEventObserver();
api.getEvent("event-all")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
private Observer<KanazawaEntity> getEventObserver(){
Observer observer = new Observer<KanazawaEntity>() {
@Override
public void onCompleted() {
Log.d(TAG,"completed");
}
@Override
public void onError(Throwable e) {
Log.d(TAG,"Error:"+e.toString());
}
@Override
public void onNext(KanazawaEntity kanazawaEntity) {
query(kanazawaEntity.getKanazawaItemses());
// List<KanazawaItems> itemse = kanazawaEntity.getKanazawaItemses();
// ArrayList<String> list = new ArrayList<String>();
// for (KanazawaItems item :itemse){
// if(item.getGroup().equals("金沢市アートホール"))
// list.add(item.getTitile());
// }
// setSimpleListView(list);
}
};
return observer;
}
private void query(List<KanazawaItems> list){
Observable.from(list)
.filter(groupFilter("金沢市アートホール"))
.map(listToStringMap())
.toList()
.subscribe(listSubscriber());
}
private Func1<KanazawaItems,Boolean> groupFilter(final String group){
Func1<KanazawaItems,Boolean> func1 = new Func1<KanazawaItems, Boolean>() {
@Override
public Boolean call(KanazawaItems items) {
if(items.getGroup().equals(group)){
return true;
}
return false;
}
};
return func1;
}
private Func1<KanazawaItems,String> listToStringMap(){
Func1<KanazawaItems,String> func1 = new Func1<KanazawaItems, String>() {
@Override
public String call(KanazawaItems items) {
return items.getTitile();
}
};
return func1;
}
private Subscriber<List<String>> listSubscriber(){
Subscriber<List<String>> subscriber = new Subscriber<List<String>>() {
@Override
public void onCompleted() {
Log.d(TAG,"completed");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(List<String> strings) {
setSimpleListView(strings);
}
};
return subscriber;
}
private void setSimpleListView(List<String> list){
ListView listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,list));
}
}
終わり
以上です!
いかがでしょうか。
初めてのRxJavaなのでこのような記述がスマートなのかどうなのか、わからないのですが一旦書いてみました。
もっと簡単に記述できるのであればぜひ教えていただけると、と思います!