前提
Android Studio 4.1.2
Kotlin 1.3.72
MVVMというアーキテクチャそのものについての解説はありません(今回初めて触れたので書けません)のでご了承ください。また、MVVMの「M(Model)」には触れず、「V(View)」と「VM(ViewModel)」のみに触れています。大体いつもは仕事で触れたことを記事にしてますが、今回は仕事ではやってなくただ興味があったのでやってみた感じです。
Lifecycleのライブラリのバージョンについてはこちらを見て、執筆時点の安定版である2.2.0を使っています。
参考にさせていただいた記事
データバインディングとは
Wikipediaにページがありました。
Wikipediaに書いてあるとおり、一言でデータバインディングと言っても「一方向バインディング」と「双方向バインディング」があります。「一方向バインディング」の方は「単方向」とも「片方向」とも言われてるようです。調べてみて人によって言い方が違うなと気付きました。この記事ではWikipediaに合わせて「一方向バインディング」で統一させていただきます。
目標
今回は入力欄とボタンを用意し、ボタンタップ時にViewModelのプロパティの値の確認と更新をするように実装してみます。
一方向バインディング(ViewModel -> View)の実現
・ViewModelのプロパティの値を更新し、入力欄にその値が表示されれば成功。
双方向バインディングの実現
・ViewModelのプロパティの値を更新し、入力欄にその値が表示されれば成功。
・入力欄に入力後、ViewModelのプロパティの値が更新されていれば成功。
実装準備
DataBindingを有効にするためにbuild.gradleを編集します。
android {
〜〜〜省略〜〜〜
dataBinding {
enabled = true
}
}
ライブラリを追加します。
dependencies { 〜〜〜省略〜〜〜 def lifecycle_version = "2.2.0" implementation 'androidx.fragment:fragment-ktx:1.3.4' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" }
一方向バインディングの実装
ViewModel
抽象クラスであるViewModelを継承したクラスを作成します。
package com.example.testapplication import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import java.time.LocalDateTime import java.time.format.DateTimeFormatter class MainViewModel : ViewModel() { companion object { private const val TAG = "MainViewModel" } // Viewと紐付けるMutableLiveData型のプロパティを用意 val mutableLiveData = MutableLiveData<String>() // Viewから実行するメソッドを用意 fun buttonClicked() { // mutableLiveDataプロパティの値を確認 Log.d(TAG, "mutableLiveData: " + mutableLiveData.value) // mutableLiveDataプロパティの値に現在日時を設定 val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss") mutableLiveData.value = LocalDateTime.now().format(formatter) } }
View
レイアウトのxmlファイルは下記のように、layoutタグでdataタグと画面を構成するタグを囲む必要があります。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="view_model" type="com.example.testapplication.MainViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{view_model.mutableLiveData}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="日時表示" android:onClick="@{() -> view_model.buttonClicked()}" app:layout_constraintTop_toBottomOf="@+id/edit_text" app:layout_constraintRight_toRightOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
Activityも書き換えます。詳細はコメントとして記載しています。
package com.example.testapplication import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import com.example.testapplication.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private val mainViewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // MainActivityとactivity_mainの紐付けと、Bindingクラスのインスタンス取得を行う。 // Bindingクラスはレイアウトのxmlファイルから自動生成される。例)activity_main.xml → ActivityMainBinding val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) // ViewModelのインスタンスを設定する。 // xml側でスネークケースで定義したnameをキャメルケースで参照できる。例)view_model → viewModel binding.viewModel = mainViewModel // MainViewModelクラスのmutableLiveDataプロパティを監視する。 // mutableLiveDataプロパティの値が更新された時、処理が実行される。 mainViewModel.mutableLiveData.observe(this, Observer { binding.editText.setText(it) }) } }
実装結果
ボタンをタップすると現在日時が入力欄に表示されることを確認できました。
また、入力欄に文字を入力してボタンをタップしてもmutableLiveDataプロパティの値が更新されてない、つまり双方向バインディングではなく一方向バインディングになっていることを確認できました。
双方向バインディングの実装
ViewModel
一方向バインディングの時と同じです。
View
レイアウトのxmlファイルとActivityの両方を一部書き換える必要があります。
レイアウトのxmlファイルは下記のように、
android:text="@{view_model.mutableLiveData}" を
android:text="@={view_model.mutableLiveData}" に書き換えます。
@ を @= にするだけです。
〜〜〜省略〜〜〜 <EditText android:id="@+id/edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={view_model.mutableLiveData}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> 〜〜〜省略〜〜〜
ActivityではmutableLiveDataプロパティを監視する処理を削除し、lifecycleOwnerを設定するコードを追記します。
package com.example.testapplication import android.os.Bundle import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import com.example.testapplication.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private val mainViewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // MainActivityとactivity_mainの紐付けと、Bindingクラスのインスタンス取得を行う。 // Bindingクラスはレイアウトのxmlファイルから自動生成される。例)activity_main.xml → ActivityMainBinding val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) // ViewModelのインスタンスを設定する。 // xml側でスネークケースで定義したnameをキャメルケースで参照できる。例)view_model → viewModel binding.viewModel = mainViewModel // MainViewModelクラスのmutableLiveDataプロパティを監視する。 // mutableLiveDataプロパティの値が更新された時、処理が実行される。 // mainViewModel.mutableLiveData.observe(this, Observer { // binding.editText.setText(it) // }) // lifecycleOwnerを設定する。 binding.lifecycleOwner = this } }
実装結果
ボタンをタップすると現在日時が入力欄に表示されることを確認できました。
また、入力欄に文字を入力してボタンをタップした時、mutableLiveDataプロパティの値に入力欄に入力した文字が設定されており、双方向バインディングになっていることを確認できました。