Android MVVM 設計 2
Android 担当の 松下です。
前回は MVVM の概念について紹介しました。今回はサンプルアプリを元に MVVM をどうやって実装していくかを紹介します。
サンプルアプリは以下です。 kotlin を使っています。 Connpass API をつかって勉強会一覧を出すだけのアプリです。
主な使用ライブラリは以下です。
- Retrofit2
- OkHttp3
- Gson
- RxJava2
- RxAndroid
- RxKotlin
- Android 公式の DataBindingLibrary
- Android Architecture Components (以下
AAC
と書きます)
前半は定番のものなので説明は不要かと思います。今回の MVVM の説明に関わってくるのは下2つのライブラリです。
まずは MainActivity から見ていきます。
class MainActivity : AppCompatActivity(), LifecycleOwner { private lateinit var mainViewModel: MainViewModel private val eventListAdapter by lazy { MainRecyclerAdapter(this@MainActivity) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) // ViewModel を取得します mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) lifecycle.addObserver(mainViewModel) // LiveData を監視し、Lifecycle.Event.ON_DESTROY 以外であればリストを更新します。 mainViewModel.events.observe(this, Observer { eventListAdapter.events = it?.events }) binding.viewModel = mainViewModel with(binding.eventList) { adapter = eventListAdapter layoutManager = LinearLayoutManager(this@MainActivity) } } }
Activity ではデータの取得などの処理は一切行っていません。そういった処理は ViewModel の責務なのでここで行うべきではないですね。
ここでやるべきことは「ViewModel の変数の監視」と「それに対応する View へ変更を促すこと」くらいです。
mainViewModel.events
は LiveData というものを使っています。これは変数の監視に使っており、この変数が更新された時、第一引数のライフサイクルが Destory でなければ第二引数のコールバックを呼ぶ、と言うものです。
MVVM 実装で重要なのはここです
mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) lifecycle.addObserver(mainViewModel)
ViewModelProviders
というのは AAC のクラスです。 ViewModelProviders.of(Activity)
とは何をしているでしょうか?
詳しい説明はまたいつかしようと思いますが、いまのところは .of(activity)
の Activity が同じインスタンスであれば同じ ViewModel が帰ってくる と理解してください。
何故このようなことをしているかというと、普通に mainViewModel = MainViewModel()
とすると、画面回転のたびに新しい ViewModel が代入されてしまいます。
前回 「ViewModel に View に使う変数を入れる」ということを書いたと思いますが、画面回転のたびに新しいものが入ってしまう(≒ 初期化されてしまう)のでは Activity と ViewModel を分けた意味が薄れてしまいますね…。
ここで注意するべきなのは、 ViewModel に Activity のインスタンスの参照などをもたせると Activity が GC されずにメモリリークを起こす可能性があります。 Context
がほしい場合は ViewModel
ではなく、 AndroidViewModel
を使って Application
を使うようです。
次に ViewModel を見ていきます。
class MainViewModel : ViewModel(), LifecycleObserver { val events = MutableLiveData<ConnpassEvent>() // ロード中かどうか val isLoading = ObservableField<Boolean>(false) private val disposeBug = CompositeDisposable() @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun getEventList() { NetUtil.connpassAPI.event() .doOnSubscribe { isLoading.set(true) } .doFinally { isLoading.set(false) } .subscribeBy( onNext = { events.postValue(it) } ).addTo(disposeBug) } override fun onCleared() { super.onCleared() disposeBug.clear() } }
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
というアノテーションがついています。これは対応している画面のライフサイクルに反応して呼ばれます。 Activity や Fragment にある onCreate
のようなものだと思ってください。 さきほど
MainActivity でlifecycle.addObserver(mainViewModel)
を記述したので、 getEventList
は MainActivity の onCreate
に反応して呼ばれます。
events
と isLoading
*1 は View に当たるクラスが監視しています。 MainViewModel はその値が何に使われているかを知る必要はなく、ただ淡々とこの値を更新するのみです。
まとめ
ViewModel は LiveData や ObservableField<*> を使って値を更新する。 Activity (View) は ViewModel の値を監視し、更新されたら View を更新する。
これだけです。簡単ですね。
次回予告
UI テストなどの際に Connpass API を叩くとその時のインターネット環境や Connpass のサーバ環境に依存したテストになってしまいます。
次回はそういった悩みを解決するべく、 Dependency Injection を試してみようと思います。
ねこじゃらしについて
ねこじゃらしでは Android に限らず Ruby (Ruby on Rails) や JavaScript での開発経験がある Web エンジニア、ネイティブエンジニア、UI/UX デザイナを募集しております。
ご興味をお持ちいただけましたら、以下のリンクからお問い合わせください。 www.nekojarashi.com
*1:LiveData が DataBinding 出来ないので ObservableField<*> を使用している