Android Architecture Blueprintsで学んだアプリ実装(MVP)

この記事について

仕事でAndroid Architecture Blueprintstodo-mvp-kotlinを腰を据えて読み込む機会があり、自分用にその内容をまとめていたのですが、どうせなら公開してしまおうと思ってこの記事を作りました。
人によっては当たり前と捉えるものもあると思いますが、あくまで自分用のメモが元なので、ご容赦願います。

これからAndroidアプリを作ろうとしている人やソフトウェアアーキテクチャとしてMVPを導入することになった人達がこの記事を見て、アプリ実装に役立ててくれたら幸いです。

なお、もしも記事内に間違いを見つけたら指摘を貰えると助かります。
(初めての記事なので、未熟な点は笑って流していただけると助かります)

Android Architecture Blueprintsとは?

そもそも、Android Architecture Blueprintsとはなんぞや? と思う人もいるかもしれないので、軽く説明します。

これはGoogleが公開しているものでTodoリストの管理アプリを様々な手法毎に実装したものです。
言語やソフトウェアアーキテクチャ毎にソースが用意されているので、初めてアプリ開発をする人や初めてのソフトウェアアーキテクチャを使う人は、これを参考にすると実装のイメージをしやすくなると思います。

正直、もっと早くに見たかったです。

本編

という訳で、前書きが長くなりましたが、ここからはtodo-mvp-kotlinを見て、私が学んだことをまとめます。
内容としては、各クラスにおいて、どのように実装がされていたかを記載しています。
なお、MVP内で実装するのが当たり前なメソッド(View上の操作を受け取ってPresenterに通知するメソッドやRepositoryにデータの取得を依頼するメソッド等)については記載してません。

Activity

MVPでの実装では、時折View層として扱われることがありますが、todo-mvp-kotlinとしては、View層の役割はなく、どの画面でもほぼ同じ実装をしていました。
その内容が以下になります。

  • Fragmentのインスタンス化とReplace
    • ただし毎回FragmentManagerでreplaseするのではなく、FragmentManagerからIDをキーに該当のFragmentを取得しようとし、取得できなかった場合にのみインスタンス化とReplaceを行う
  • Repositoryのインスタンス化
  • Presenterのインスタンス化
    • 詳しくはPresenterの項目で記載しますが、Activityでインタンス化したFragmentとRepositoryを引数にとっています

補足

面白いと感じたのは、Activityで使うFragment(View)、Presenter、Repository(Model)は全てActivity上でインスタンス化されていること。
Activityを見るだけで、そこで使われているものが分かるようになっていました。

Fragment(View層)

MVPにおけるViewの役割を担っているのがFragmentでした。
todo-mvp-kotlinは簡単な画面のみなので、1Activityに対し1Fragmentで実装されていましたが、この辺は、画面が複雑化することによって、変わってくるかもしれません。
画面ごとに細かな実装は異なりますが、概ね以下のように実装がされていました。

  • Viewのinterfaceのさらに親のinterfaceがあり、Presenterの型でcastされるinterfaceを全てのViewのinterfaceが継承している
interface BaseView<T> {
    var presenter: T
}
  • 必ず、そのFragmentが生きている(Active)かを示すメンバ変数がinterfaceに用意されている
    • ただし、Fragment自体がそのメンバ変数を使うことはない

補足

驚いたのはPresenterをメンバ変数に用意しておきながら、そのPresenterをセットする処理はどこにもないということ。
lateinitで定義されたメンバ変数が存在するのに初期化する処理が見当たらないというのは、最初に見たときは面食らいました。
この辺は、Presenterがやっているので、そちらで説明します。
同様に、Fragmentが生きている(Active)かを示すメンバ変数が用意されているのも少し不思議に感じました。
今まで、私は、必要になったらFragmentクラスが持っているメソッドを使っていたので、あえてメンバ変数にする理由はちょっと良く分かっていないです。
ご存知の方がいたら教えて欲しいですね。

Presenter(Presenter層)

アプリを実装するうえで最も重要なものの一つと言えるPresenter。
todo-mvp-kotlinで見たときは、ちょっと混乱することになりました。
読めば読むほど、今まで自分がMVPとして作ってきたアプリの書き方を間違っているという可能性がありそうで、まだ理解が足りていない部分があるように感じています。
実装の内容は以下になります。

  • Presenterのinterfaceのさらに親のinterfaceがあり、開始を意味するメソッドが定義されており、全てのPresenterのinterfaceが継承している
  • プライマリコンストラクタの引数に必ずRepositoryとFragment(のinterface)が定義されている
  • 必ずinitでプライマリコンストラクタに定義されているfragmentのpresenterに自分自身をセットしている
  • Repositoryを使うときはコールバック関数を用意して結果をうけとり、Viewを操作する場合はFragmentで定義されているFragmentが生きているかを示すメンバ変数を使って、生きていることが確認できた場合にのみViewの操作を依頼する

補足

非常に悩んだのが、Fragmentのメンバ変数をPresenter側で初期化するという点でした。
正直、Fragment側のonCreateでPresenterを初期化するのがなぜダメなのかというのが分かっていません。
強いて言うなら、ActivityでFragment、Repository、Presenterを一気にインスタンス化する場合に、ライフライクルに縛られずPresenterをセットできるからと感じましたが、理由としては弱いかなと思っています。
ご存知の方教えていただけたらなと思います。

Repository(Model層)

Repositoryですが、todo-mvp-kotlinは実装例として簡易的に作られたアプリなので、当然、APIのようにサーバーにデータを置いて、ネットワークを介して保存、更新、取得を行うというサービスはありません。
なので、端末内にDBを用意し、それを疑似的にネットワークからデータを取得したかのように振る舞わせるようにしています。
実装内容は以下になります。

  • シングルインスタンスのクラスだがobject classとして定義するのではなくcompanion objectを使ってシングルインスタンスを実現している
  • DBとAPIを引数にgetInstanceメソッドを介して利用するようにデザインされている
  • キャッシュ更新するフラグとなるDirtyフラグがあり、画面のリロード処理等が行われたタイミングでフラグがONになるようになっている
    • その後、新しいデータを取りに行く処理が走るが、Dirtyフラグだけ個別でONにする処理が用意されている

補足

Kotlinの場合、classを定義する際にobjectとすれば、シングルインスタンスを実現できますが、Repositoryではあえてそれを行っていませんでした。
これは、Repositoryを単体テストする際に、DBとAPIのmockをgetInstanceの引数として渡すことで、実際のデータを汚さずにテストできるようにするためだと思われます。
また、API(もどき)から強制的にデータを取得する場合、先にDirtyフラグを立てる処理だけ動かして、その後、APIからデータを取得するようにデザインされています。
これは、途中で通信に失敗し、処理が途切れたとしても再表示時にキャッシュを捨てて、APIから取得できるようにするためだと思います。

後書き

※個人的所感を書いてるだけなので、読まなくても大丈夫です

最初に書いた通り、機会があってtodo-mvp-kotlinを熟読しましたが、読めば読むほど自分の実装の粗が見つかって悲しくなりました。
まだ、実装の意図を理解できていない部分もあるので、そこは要勉強といったところですかね。

あと、Qiitaに記事をあげるの初めてなので、字ばっかり+読みづらい……。

今後も更新できたらなとは思いますが、この辺は慣れなんでしょうかね。

愚痴が続いてしまいましたが、以上でこの記事は終了になります。
まだ、テストコードについて書いてないので、次はその記事が書ければと思います。
それでは、ありがとうございました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.