Android MVVM 設計 2

Android 担当の 松下です。

前回は MVVM の概念について紹介しました。今回はサンプルアプリを元に MVVM をどうやって実装していくかを紹介します。

nekojarashi.hatenablog.jp

サンプルアプリは以下です。 kotlin を使っています。 Connpass API をつかって勉強会一覧を出すだけのアプリです。

f:id:nekojarashi-Inc:20180119175830p:plain

github.com

主な使用ライブラリは以下です。

  • 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.eventsLiveData というものを使っています。これは変数の監視に使っており、この変数が更新された時、第一引数のライフサイクルが 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 に反応して呼ばれます。

eventsisLoading *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<*> を使用している