Androidアプリ開発初級編(ViewModel+LiveData+DataBinding)

今回はAACのViewModel、LiveDataを使った簡単なアプリを使って使い方について学習したいと思います。

■ViewModelとは

ViewModelとは Android Archtecture Components の一つで、UIデータの保持ができ onSaveInstanceStateよりも便利で使いやすいです。またLiveDataやDataBindingを組み合わせることでよりコードの複雑さを解消できUI周りのモジュール間の関心の分離に対して効果を発揮します。ライフサイクルと適応しているため、手動でのライフサイクル管理が不要。

※Android公式サイト参照

■LiveDataとは

LiveDataとは Android Archtecture Components の一つで、データの観測を目的とし、値の変化を検知して何かを処理する際に効果を発揮します。ライフサイクルと適応しているため、手動でのライフサイクル管理が不要。

※Android公式サイト参照

■事前準備

前回のNavigationサンプルアプリをそのまま使ってViewModelを追加していきます。

■サンプルアプリの動作

0~100の数字を2つ選択して、計算を行う

■ViewModelの作成

FragmentCに対して下記のViewModelを作成します。

計算用のIntを二つと、計算結果のテキスト、除算判定用のBooleanを管理しています。

Udacityでも取り上げられていましたが、必ずカプセル化を意識して値を管理してください。決して直接FragmentやActivityから値を操作しないように。

class CalcDivisionTimesModel(val firstValue: LiveData<Int>, val secondValue: LiveData<Int>) :
    ViewModel() {
    private var _calcResultText = MutableLiveData<String>()
    val calcResultText: LiveData<String>
        get() = _calcResultText

    private var _isCalcDivision = MutableLiveData<Boolean>()
    val isCalcDivision: LiveData<Boolean>
        get() = _isCalcDivision

    init {
        _calcResultText.value = (firstValue.value!!.times(secondValue.value!!)).toString()
        _isCalcDivision.value = false
    }

    fun onClickDivision() {
        _calcResultText.value = (firstValue.value!!.div(secondValue.value!!.toFloat())).toString()
        _isCalcDivision.value = true
    }

    fun onClickPow() {
        _calcResultText.value = (firstValue.value!!.times(secondValue.value!!)).toString()
        _isCalcDivision.value = false
    }
}

onClickDivisionやonClickPowはDataBindingを用いて紐づけを行います。

    <data>

        <variable
            name="calcViewModel"
            type="com.tomostudy.navigationtest.minus.CalcDivisionTimesModel" />
    </data>
  
  ....

        <Button
            android:id="@+id/pow_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="52dp"
            android:layout_marginEnd="24dp"
            android:onClick="@{() -> calcViewModel.onClickPow()}"
            android:text="@string/pow"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/division_button"
            app:layout_constraintTop_toBottomOf="@+id/result_text" />

またLiveDataを使うことにより、データをバインドするだけで値が変更した際にUIの更新まで行うことが可能です。

        <TextView
            android:id="@+id/result_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{calcViewModel.calcResultText}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/calcSignText" />

ViewModelに引数を渡しているので、今回はViewModelFactoryを作成していきます。ViewModelProvider.NewInstanceFactoryを使って作成。

class MinusViewModelFactory(private val firstValue: Int, private val secondValue: Int) :
    ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return CalcDivisionTimesModel(
            MutableLiveData(firstValue),
            MutableLiveData(secondValue)
        ) as T
    }
}

■Fragment側の実装

次にFragment側の実装を見ていきましょう

bindingオブジェクトのインスタンスにviewModelを代入することでデータバインディングを設定。bindingにライフサイクルを設定することでLiveDataのライフサイクルが管理されるようになります。これをしないとLiveDataを元にUIが更新されないので注意してください

次にViewModel内のLiveDataに対してオブザーバーを設定し、値の変更時にフラグメント側でリソースを更新しています。

このようにViewModel+LiveData+DataBindingにより従来よりも遥かにコードがすっきりしています。

class FragmentC : Fragment() {
    private val viewModel: CalcDivisionTimesModel by viewModels {
        MinusViewModelFactory(
            args.firstValue,
            args.secondValue
        )
    }
    private val args: FragmentCArgs by navArgs()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentCBinding =
            DataBindingUtil.inflate(
                inflater,
                R.layout.fragment_c, container, false
            )
        binding.calcViewModel = viewModel
        binding.lifecycleOwner = viewLifecycleOwner
        binding.calcViewModel!!.isCalcDivision.observe(
            viewLifecycleOwner,
            Observer { isCalcDivision ->
                if (isCalcDivision) {
                    binding.calcSignText.text = getString(R.string.division_sign)
                } else {
                    binding.calcSignText.text = getString(R.string.pow_sign)

                }
            })
        return binding.root
    }
}

■まとめ

・ViewModelによりUIデータを保持することが出来る

・LiveDataによりデータを観測して処理を制御出来る

・これらと共にDataBindingを組み合わせることでActivity/Fragmentの肥大化を防ぎ、同時にモジュール間の関心を分離することが出来る。

・ライフサイクル管理を手動で制御せずに済む

アプリ開発を始めたばかりで、アーキテクチャを知らなくてもアプリ構成がMVVMになるはずなのでコードが強制的にすっきりするのが本当に良いですよね。他にもライフサイクル管理という面倒で複雑化の要素になっていたものが解決されているのもすごい。是非これらを活用してアプリ開発をより良くしていきましょう。

それでは。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です