本記事は
モバイルアプリWeek
2日目の記事です。
📲
1日目
▶▶ 本記事 ▶▶
3日目
📲
はじめに
ネットコムのブログが出来てからはじめてのブログ執筆になります、クラウド部の西村です!
私は去年の夏から右も左も分からないままAndroid開発に携わるようになり、初期の頃はMVVMに対して「自分の作っているものがMVVMというものを採用しているらしい!」ぐらいのかなりぼんやりとした認識しかありませんでした。
ですがAndroidエンジニアとしての1年目がそろそろ終わりそうな今、MVVMをある程度理解し、加えて理解度に比例して開発効率が上がったな〜という実感もあるので、今回整理と備忘のためにブログにします。
MVVM とは
MVVMとは、Model - View - ViewModel から構成されるアーキテクチャパターンで、各要素は以下のような役割を担っています。
Model: ビジネスロジック担当
View: UI担当
ViewModel: View とModelの橋渡し担当
このように各要素で関心を分離し、単一方向の依存関係を保ち、さらにデータフローがシンプルになるよう整えることで、どこに何の処理が書いてあるかが追いやすく開発効率を上げることができるのがMVVMの旨味です。
以下では各要素間のやり取りを切り分けて見ていきます。
ViewとViewModel
ViewとViewModelはそれぞれ以下のような役割を担います。
Viewの役割
- UIの描画
- UIから受け取ったユーザー操作をViewModelに伝える
- ViewModelが保持しているUIの描画に必要なデータを監視する
ViewModelの役割
- UIの描画に必要なデータの保持
- 上記の準備(データの加工)と更新
ここでUIの描画に必要なデータと呼んでいるものは要するに画面の状態のことで、表示したいテキストや可視不可視などをUIに反映しやすい状態でデータとして持ったものです。
MVVMではView → ViewModelの単一方向の依存関係を保ちたいので、ViewModelで保持するデータを元にViewでUIの描画をしたいときに、ViewModelからViewを操作するのはNGです。
ではどのようにデータのやり取りをするのかというと、ViewModelに保持しているUI描画用のデータをViewから参照してUIを描画します。 そして、これをAndroidアプリで実現するための最もオーソドックスな方法が、DataBinding + LiveDataです。
DataBinding
DataBindingはレイアウトファイルにUIの描画用のデータをバインディングするAndroid Jetpackのライブラリです。
データ バインディング ライブラリ | Android デベロッパー | Android Developers
レイアウトファイルをDataBinding用の形で書いておけば、それに合わせてデータをバインドするためのクラスを自動で生成してくれるので、 あとはUIの描画に必要なデータを渡しておけばレイアウトに勝手に反映してくれます。
これの何が良いかと言うと、DataBindingを使用しない場合はView要素(TextViewなど)にあらかじめidを振っておき、idを元にfindViewByIdでView要素を特定して、表示するテキストを指定したりリスナーをつけたりするのがよくある話ですが、DataBindingを使えばこの過程を全て省略することができます。
もちろんこの過程を省略することでコードも減るので、ActivityやFragmentといったViewに該当するクラスのロジックをシンプルに保つことができ、前述したUIの描画などのViewの関心事に専念することができるのも旨味です。
LiveData
DataBindingを使えばシンプルにデータをUIに反映できるようになりますが、UIに反映させるデータを更新したいときにどうすれば良いのかが気になります。そこで使うのがLiveDataです。
LiveDataはAndroid Jetpackが提供するデータホルダークラスで、LiveDataで保持したデータは、Viewのライフサイクルを意識しながら監視を行うことができます。
LiveData の概要 | Android デベロッパー | Android Developers
LiveDataで保持したデータにはデータを監視して変更を検知するメソッド(observer)があり、これにlifecycleOwnerを渡しておくとView側のクラスのライフサイクルが生きている間はLiveDataで保持したデータの監視と変更の検知をしてくれるので、変更を検知したタイミングでUI描画用のデータをDataBindingでバインドし直せばUIが更新されます。
ActivityやFragmentが生きていない間は変更の検知をしないので、データをバインドする前にActivity/Fragmentが生きているかどうかを一々チェックする必要がなく、ライフサイクル管理が楽になります。
(補足)lifecycleOwner
Viewを担うクラスのActivityやFragmentはlifecycleOwnerを持っており、lifecycleOwnerをViewModelに渡すことでViewのライフサイクルイベントに応じた処理をViewModelでも実行できるようになります。
UIの描画に必要なデータをViewModelで持っておくと何が良いか?
ViewModelのライフサイクルはActivityやFragmentよりも長く、UIの描画に必要なデータをViewModeに持っておくことで、画面回転でActivity/Fragmentが破棄された場合でもすぐにUIの再描画が可能です。
逆に、ViewModelではなくActivityやFragmentでデータを保持している場合、「APIを叩いてデータを取ってきて、、、」、「UI用にデータを変換して、、、」というような、はじめてUIを描画するのに必要なすべての処理を再度実行する必要が出てきます。
どうしてもViewModelからViewを操作したい場合
ViewModel - Modelの間の処理に応じて、ViewModelからViewに対して何かしらの描画指示をどうしても出したい場合もあるかと思います。
例えばエラー発生時にエラーダイアログを出したり、何かしらの処理が完了したタイミングでスナックバーを出したりする場合です。
View ← ViewModelに対するメッセージングを実現したい場合、LiveDataを用いてViewModelからイベントを通知(している風に)することができます。
自分で実装しても良いですが、LiveDataの仕組みを使ってViewModelからViewに対してイベントの通知を行えるLiveEventなどのライブラリを利用して簡単に実装できます。
このように、MVVMでAndroid開発をする場合、UI描画に必要なデータ(画面の状態)をLiveDataでViewModelに保持し、それをViewで監視して適宜DataBindingでUIに反映させることでView → ViewModelの単一方向の依存関係を実現できます。
ViewModelとModel
ViewModelとModelは具体的にはそれぞれ以下のような役割を担います。
ViewModelの役割
- ビジネスロジックの入力値の変換、実行指示、返却値の受け取り
- Modelが保持する値の参照
Modelの役割
- ビジネスロジック(APIから情報の取得、DBの操作など)の実行
- data class を定義し取得したデータをアプリ内で取り回しの良い状態に変換
ViewModel → Modelの場合も、View → ViewModelと同様、ViewModelからModelの処理を実行してデータを取得したり、ViewModelで必要な情報をModelで保持するデータを参照して取得したりします。 こうすることで、もちろんViewModel → Modelの単一方向の依存関係になります。
まとめ
MVVMではそれぞれの要素に対して下記のように関心の分離を行います。
View
- UIの描画
- UIの操作を受け取ってViewModelに伝える
- ViewModelで保持しているUI描画に必要なデータを監視し変更を検知する
ViewModel
- UI描画に必要なデータの保持と更新
- Modelに対してビジネスロジックの実行を指示
- Modelの保持するデータの参照
- ViewとModelの間で必要なデータの変換
Model
- ビジネスロジックの実行
- アプリ内で取り回すデータの定義
また、データフローの上流側が下流側に依存関係を持ち、下流側の要素の操作とデータの参照を行うことで単一方向の依存関係とシンプルなデータフローが実現を実現できます。
特にAndroidの場合はView - ViewModel間においてDataBindingとLiveDataを組み合わせて使用することで、Viewのロジックを減らしつつ単一方向の依存関係を簡単に実現できます。
終わりに
MVVMを理解する前はどのクラスに何を実装すべきか迷うことも多く、特にViewにちょっとしたロジックを書いてしまうことや、ViewModelからFragmentの操作をしてしまうことが多かったです。 ですが、Model - View - ViewModelでどのように関心の分離と単一方向の依存関係の実現し、それに伴ってどのようなデータフローになるかを理解したことで実装に迷いがなくなりました。
また、他の開発者の実装がコードを見なくてもなんとなく想像できるので、どこにどういう処理が描かれているか見つけやすくなり、すでにあるコードの修正や、自分がやりたい実装の参考になるコードの発見、コードレビューのスピードが上がった実感があります。
Model内の話はまだまだ分からないことが多いので、これから理解を深めていきたいです!
ぼやき
Android開発に携わるようになる前はVue.jsでWebアプリケーションを作っていたのですが、Vue.jsもMVVMを前提としたフレームワークなので、MVVMをなんとなく理解した今振り返るともっとこうすればよかったな〜とか、あれはああいう意味だったんだな〜と思ったりします。