Android: MVP, Dagger2, Retrofitなどなどでアプリを作りました

イベントを検索するAndroidアプリを作成したので, 使った技術のまとめです.

アプリの技術的機能, 特徴は以下になります.

上記を中心にどのように実装をしたかを説明をしていきます.

フルソースコードはここにあります. https://github.com/satoshun/AndroidEvents

HTTPを介してイベントのデータを取得する

connpass, Atnd, Zusaarの3つのAPIを使うことにしました. RetrofitでAPIを定義し, JSONのパースにはGson, データの処理にはRxJavaを使いました.

例えば, connpass APIは以下のように定義しました.

/** Get data from Conpass  */
public interface Connpass {
    @GET("/v1/event")
    Observable<ConnpassResponse> search(
            @Query("ymd") List<String> ymds);

    @GET("/v1/event")
    Observable<ConnpassResponse> search(
            @Query("keyword") String keyword,
            @Query("ymd") List<String> ymds);
}

keywordとymdを指定してリクエストを生成します. 「2015/12/31のAndroidのイベント」のように使う想定です. Retrofitを使うと, このようにinterfaceとして, APIを定義することが出来ます. (https://github.com/satoshun/AndroidEvents/blob/master/app/src/main/java/com/github/satoshun/events/network/Connpass.java#L10)

次に, RxJavaの説明をします. RxJava(Observable)を使うと, 非同期にデータが扱いやすくなります. またデータのマージ(merge)や, フィルタリング(filter)などを簡単に行うことが出来ます. 今回は, 3つのAPI(connpass, Atnd, Zusaar)が終わるのを待ってから処理を開始したかったので, 以下のように書きました.

Observable.merge(
  connpass.search(keyword, generateYmd()),
  atnd.search(keyword, generateYmd()),
  zusaar.search(keyword, generateYmd()))
  .subscribe(...)

Observable.mergeは複数のObservableを1つのObservableにまとめるAPIです. これで, 3つのAPIが終了するまでwaitすることが出来ます. あとは, これをsubscribeして, データを取得します.(https://github.com/satoshun/AndroidEvents/blob/master/app/src/main/java/com/github/satoshun/events/ui/domain/EventInteractor.java#L41)

Data-Bindingライブラリを使う

Data-BindingはXMLレイアウトにbindしたいデータ(インスタンス)を記述することで, よしなにデータを出力してくれる機能です. AngularJSのデータバインディングをイメージして貰えると良いと思います. 書くコード量が減り, とても便利なライブラリでした. (https://github.com/satoshun/AndroidEvents/blob/master/app/src/main/res/layout/adapter_event.xml)

また, 推奨された使い方かどうかはわからないですが, ViewHolderパターンとして使うことも出来ます. ViewHolderパターンは, Adapter#getViewでコストが掛かる処理(View#findViewByIdなど)をcacheするパターンです.

public class EventAdapter extends BaseAdapter {
    /*
      .. ....
     */

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        if (view == null) {
            AdapterEventBinding binding = AdapterEventBinding.inflate(inflater, parent, false);
            view = binding.getRoot();
            view.setTag(binding);
        }

        AdapterEventBinding binding = (AdapterEventBinding) view.getTag();
        Event event = getItem(position);
        binding.setEvent(event);
        binding.setDateFormat(DATE_FORMAT);

        return view;
    }
}

AdapterEventBindingをViewHolderの代わりに使っています. (https://github.com/satoshun/AndroidEvents/blob/master/app/src/main/java/com/github/satoshun/events/ui/adapter/EventAdapter.java#L34) なかなか良いと思います.

MVP(Model-View-Presenter)パターンを使う

MVPパターンとは, MVCの親戚?のようなパターンで, 責務をModel, View, Presenterにそれぞれ分割するパターンです.

Android開発は, Activity(Fragment)の責務が大きくなりがちです. 具体的にはActivityは以下のよう責務を持ちます.

これらを全て1つのActivityで処理をすると, どうしてもFat-Activityになってしまいます. (1つのAcitvityが1000行ありますみたいな)

そこでMVPです.

このように責務を分割することで, Activityの責務が薄くなります. 今回のコードも今まで自分が書いたコードと比較すると, 大分良くなった気がします(当人比)

その他, メモ

Dagger2

DI(Dependency Injection)をするためのツールで, 実装をデバッグ時, 本番時, テスト時に切り替えられるライブラリです. デバッグ時はデータの取得先を変えたい, テスト時にはネットワークアクセスしないで欲しい, などといった時に力を発揮します.

まとめ

MVPパターンの話をしましたが, MVPが絶対良いという話ではないです. しかし, 何もパターンがないとFat-Activityになってしまったり, 無秩序なコードになってしまいがちなので, そのような場合は, MVPのようなパターンを導入したほうが良いと思います.

まだアプリ自体はまだ付けたい機能があるため, 公開していません. 近々しようと思います.

references