家族ToDo(仮)開発日誌

やりたいことや行きたい場所を家族で共有するためのAndoridアプリの開発日誌(兼Android開発学習メモ)です。

開発日誌(3) : モデルの追加とDI

今回の進捗

作業時間: 1.5h

モデリング

昨今はMVVMとか最近流行(再燃?)らしいが、フレームワークの高度なサポートがないと実装が面倒だという話をどこかで聞いたので、慣れたMVC(偽)な感じで作ろうと思う。

まずはListViewにデータを表示すべく、データモデルを考える。このListViewにはToDoアイテムの一覧を表示したいので、ToDoアイテムのモデルを考える。

f:id:ymkn:20140114235001p:plain

とりあえずこんな感じで良かろう。将来的にはToDoなのかToGoなのかToBuyなのかを区別できるようにしたいし、Ownerが誰かを示すuserなんかもほしいところだが、今日はListViewの動作を確認するのが目的なのでこの程度で良いだろう。

modelパッケージを掘って、クラスを書く。ここでまたAndroid Studioに感動してしまったのだが、コードエディタ上でCommand+Nを押すと小窓が現れてgetter/setterの自動生成なんかが簡単にできたりする。他にもコンストラクタ生成やメソッドのオーバーライドなんかもここからできる。これらが個別のショートカットキーで起動するわけではなく、すべてCommand+Nでいけるのが個人的にはすばらしい。困ったらとりあえずCommand+N押しとけ、みたいな。

AndroidでDI

上記で設計したToDoクラスを得るためのFactoryを作るのだが、とりあえずは適当なToDoを作って返すテスト用のFactoryを作っておき、後々Webから取得する本物のFactoryに差し替えたい。Mockとか使うと良さそうな案件だが正直よくしらんのでDIすることにする。

Dagger

最終的にデータはサーバサイドで管理するので、画面に表示するデータはサーバ側からAPI経由で取得させることになるが、とりあえずAndroidアプリを動かすところから始めたいので、適当にデータを供給するProviderとそれを生成するFactoryを作ろうと思う。まずは適当なデータを返すProviderを使うようにしておき、あとでパラメータか設定ファイルかビルドオプションか何かでサーバ側からデータを取得するProviderに差し替えられるようにしたい。

で、こういうときはやっぱりDIしたくなるわけで、AndroidDIコンテナについて適当に調べた。Guiceが有名のようだが、下記サイトによればDaggerのほうが全ての面において優れているとのことでこれを使うことにした。ライブラリのサイズが小さいようですばらしい。

Dagger

Android Studioのプロジェクトで使えるようにするには、jarを入れるディレクトリ(ここではapp/libsとした)を作ってdagger-1.2.0.jarとdagger-compiler-1.2.0.jarを放り込み、jarを右クリックして[Add as Library]を選べばよい。これでgradleの設定ファイルにエントリが追加され、ビルド時に参照されるようになる。

で、上記だけだとビルド時にライブラリが足らんと怒られるので、javawriter-2.3.1.jarとjavax.inject.jarを入手して追加する必要があった。
(javax.inject.ScopeのNoClassDefFoundErrorがでる)

javax.inject-1-bundle.jar - atinject - Maven bundle for javax.inject - JSR-330: Dependency Injection for Java - Google Project Hosting

ちなみにDagger専用のAnnotationをコードエディタ上で補完できるようにするには、IntelliJ用のプラグインを使うと良いみたい。

square/dagger-intellij-plugin · GitHub

DaggerのAnnotation

あまりDaggerのドキュメントをまじめに読んでいないのだが、とりあえずサンプルコードを見る限り、

  • インスタンス生成ロジックを記述しておくModuleクラス (classに@Module(injects = Inject対象のクラス)を、メソッドに@Providesを付与。メソッド名はprovide***じゃないとダメっぽい?)
@Module(injects = KazokuToDoController.class)
public class KazokuToDoModule {
    private String type = "local";

    public KazokuToDoModule(String type) {
        this.type = type;
    }

    @Provides
    public UserProvider provideUserProvider() {
        UserProvider provider = null;
        if ("net.jibunstyle.kazokutodo".equals(this.type)) {
            // TODO: 本物を返す
        } else {
            provider = new LocalUserProvider();
        }
        return provider;
    }

    @Provides
    public ToDoProvider provideWishListProvider() {
        ToDoProvider provider = null;
        if ("net.jibunstyle.kazokutodo".equals(this.type)) {
            // TODO: 本物を返す
        } else {
            provider = new LocalToDoProvider();
        }
        return provider;
    }
}

で、Injectしたいフィールドに@Injectをつけておく。このときModuleクラスからアクセス可能な可視性である必要がある模様。privateだと怒られたのでControllerとModuleを同じパッケージに入れて、@Injectなフィールドはpackageレベルの可視性にした。

public class KazokuToDoController {
    @Inject UserProvider userProvider;
    @Inject ToDoProvider toDoProvider;
(略)
}

これでいい感じにインスタンスが挿入される。のちのち、Moduleクラスのprovideメソッドの中で外部設定ファイルを読み込み、インスタンス化するクラスを変更できるような仕掛けにしようと思うが、どうも泥臭い。なんかもっとすっきりできる方法があるのではないかと思うが、現時点では本質的でないので後回しにする。