ViewModelとLiveDataを試してみました。
ViewModelとLiveDataは個別に使うこともできるのですが、セットで使われる事が多いようなので一緒に試してみました。
ViewModelを利用する
まずはViewModelを使うためにモジュールのbuild.gradleに下の1行を追加します。
implementation "androidx.activity:activity-ktx:1.4.0"
ViewModelは下のように定義します。
class MyViewModel: ViewModel() { var test = 0 }
利用方法は下の通りです。
ActivityのviewModelsに委譲する形で変数を定義します。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val vm: MyViewModel by viewModels() Log.d("Test", "${vm.test}") // → 0 vm.test = 1 Log.d("Test", "${vm.test}") // → 1 val vm2: MyViewModel by viewModels() Log.d("Test", "${vm2.test}") // → 1 } }
viewModelsはActivityの保持するプロパティーなので、下コードのvmとvm2の持つプロパティーは共通になります。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val vm: MyViewModel by viewModels() Log.d("Test", "${vm.test}") // → 0 vm.test = 1 val vm2: MyViewModel by viewModels() Log.d("Test", "${vm2.test}") // → 1 } }
LiveDataを利用する
LiveDataを使うと値の変更があった時、自動的に画面の表示も更新できるようになります。
まずはBindingを使うためにbuild.gradleでBindingを有効にします。
android { ... dataBinding { enabled = true } }
次はLiveDataを使い、下のようにViewModelを定義します。
class MyViewModel: ViewModel() { val myProperty = MutableLiveData("test") }
レイアウトファイルでViewModelの変数を使う方法は下の通りです。
variableタグで変数を定義して@{}の内部でそのプロパティーにアクセスしています。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="com.example.myapp.MyViewModel"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{vm.myProperty}" /> </LinearLayout> </layout>
xmlへViewModelを渡す処理は下の通りです。
lifecycleOwnerがないとLiveDataがうまく動かないので注意が必要です。
class MainActivity : AppCompatActivity() { val vm: MyViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.lifecycleOwner = this binding.vm = vm } }
起動すると画面にmyPropertyの値が表示できています。
LiveDataの値は下のように書き換えます。 書き換えるとViewも即時更新されます。
vm.myProperty.value = "test2"
ビュー側の変更をViewModelに反映するには@{}ではなく@={}を利用します。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="com.example.myapp.MyViewModel"/> </data> <LinearLayout android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={vm.myProperty}" /> </LinearLayout> </layout>
Activity側でのプロパティー変更の監視方法は下の通りです。
myPropertyの値が変更された時に下の処理が呼ばれます。
vm.myProperty.observe(this) { Log.d("Test", it) }
別のプロパティーに依存するLiveDataの定義ではMediatorLiveDataを使います。
例えば入力した文字数をリアルタイムで表示したい場合、下のようにtextCountを監視します。
ここで利用しているaddSourceメソッドはobserveと違い引数にLifecycleOwnerが不要です。
そのため下のようにViewModel内で処理を完結する事ができます。
class MyViewModel: ViewModel() { val myProperty = MutableLiveData("test") val textCount = MediatorLiveData<Int>() init { val observer = Observer<String> { textCount.value = myProperty.value?.count() ?: 0 } textCount.addSource(myProperty, observer) } }
レイアウトは下のように記述します。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="vm" type="com.example.myapp.MyViewModel"/> </data> <LinearLayout android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={vm.myProperty}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{vm.textCount.toString()}" /> </LinearLayout> </layout>