MVVM 完全に理解した - 7
前回 Android Architecture BluePrints を読んで挫折した(早っ)ので、基本に立ち返り最もシンプルな構成で Data Binding を体験してみます。
作るのは、MainActivity 1枚だけのミニアプリです。 純粋にリファレンスに沿って実装しました。
機能と外観
機能は、 EditText に入力した文字列をすぐ下の TextView に表示するだけのものです。
次の画像では hint を表示しているので違う文が表示されていますが、入力すると同じ文字列が表示されます。
手順
MainViewModel.kt
の作成
たぶん最初に行うのが、ViewModel クラスの作成だと思います。
TextViewのtext要素
にバインドさせるため、inputText
というフィールドを定義します。
class MainViewModel : ViewModel() { val inputText = MutableLiveData<String>() fun setInputText(s: String) { inputText.value = s } }
これだけ。
inputText.value
でバインドされている要素に更新通知がなされます。*1
inputText
はできればprivate
にしたいところですが、後述のとおり外部からobserve
メソッドでセットするのでなんとも言えないところ。BluePrints でもpublic
になってるしそういうものなのかな?
activity_main.xml
の作成
MainActivity のレイアウトファイルを作成します。
ほとんどの場合 UI を Activity 上に直接載せることは少ないかと思いますが、リファレンスに沿うと Data Binding の練習はこんな形のスタートになると思います。
<layout xmlns:android=...> <data> <variable name="viewmodel" type="work.kcs_labo.mvvmpractice.MainViewModel" /> </data> <android.support.constraint.ConstraintLayout ...> <EditText android:hint="文字を入力してくだしあ" android:id="@+id/input" .../> <TextView android:hint="ここに反映されます" android:text="@{viewmodel.inputText}" android:id="@+id/output" .../> </android.support.constraint.ConstraintLayout> </layout>
Data Binding のためのレイアウトファイルでは、layout
要素をルートにし、data
以下を記述します。
android:text="@{viewmodel.inputText}"
というところでバインドを行っています。
ここまで終わったら、念のためリビルドしActivityMainBinding
クラスが自動生成されていることを確認します。
MainActivity.kt
の作成
最後にMainActivity
を作成します。onCreate
に全部詰めた形であまり見てくれはよくないですが、最も簡単な構成になると思います。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //Viewの生成(inflate) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) //または次も可 //val binding = ActivityMainBinding.inflate(layoutInflater) //MainViewModelの生成 val viewmodel = ViewModelProviders.of(this).get(MainViewModel::class.java) viewmodel.inputText.observe(this, Observer { input -> if (input != null) { //UI更新 binding.output.text = input } }) //BindingインスタンスにViewModelを追加 binding.viewmodel = viewmodel //TextWatcherで入力監視 input.addTextChangedListener(object : TextWatcher { ... override fun onTextChanged(s: CharSequence?, ...) { //ここでBinding#viewmodelを参照 if (s != null) { binding.viewmodel?.setInputText(s.toString()) } } }) } }
少し長いので、要素ごとに見てみます。
View の生成
//Viewの生成(inflate) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) //または次も可 //val binding = ActivityMainBinding.inflate(layoutInflater)
もともとあったsetContentView
をDataBindingUtil.setContentView<T>
に置き換えます。
簡単に言えば、Data Binding Layout File を使う場合、DataBindingUtil.setContentView<T>
を用いてViewを生成します。
ViewModel の生成
次に ViewModel を生成します。
ViewModel はActivity#onCreate
で生成するのが鉄則です。
//MainViewModelの生成 val viewmodel = ViewModelProviders.of(this).get(MainViewModel::class.java)
ViewModel のインスタンス化は、ViewModelProviders
の静的メソッドにより生成します。
値更新時の動作の実装
次に、inputText (MutableLiveData型)
のsetValue
メソッドが呼ばれた(=値が更新された)ときの動作を定義します。
viewmodel.inputText.observe(this, Observer { input -> if (input != null) { //UI更新 binding.output.text = input } })
binding.output
は TextView です。text
要素を入力された値(input)に更新します。
これにより、「何らかの動作」でinputText.setValue
がコールされたとき、Observer
の匿名メソッドが読まれてoutput
が更新されます。
「何らかの動作」部分は、TextWatcher にて実装します。(後述)
Binding に ViewModel をセット
次に、View と ViewModelを紐づけます。
これによりbinding.viewmodel?.[メソッド]
といった形で、ViewModel のメソッドがコールできるようになります。View への処理を ViewModel に委譲することができます。*2
//BindingインスタンスにViewModelを追加
binding.viewmodel = viewmodel
「何らかの動作」の実装
今回は EditText を入力として使っているので、EditText に TextWatcher
を追加して随時イベントが発生するようにしてみます。*3
//TextWatcherで入力監視 input.addTextChangedListener(object : TextWatcher { ... override fun onTextChanged(s: CharSequence?, ...) { //ここでActivityMainBinding#viewmodelを参照 if (s != null) { binding.viewmodel?.setInputText(s.toString()) } } })
binding.viewmodel?.setInputText
により次のメソッドが呼ばれます。(再掲)
class MainViewModel : ViewModel() { val inputText = MutableLiveData<String>() fun setInputText(s: String) { inputText.value = s } }
これで入力に応じて随時同じ文字列が出力されるようになりました。長い。
制御の流れ
文字列を入力する
↓
TextWatcher のbinding.viewmodel?.setInputText(s.toString())
が呼ばれる
↓
inputText.value = s
が読まれる
↓
更新通知
↓
Observer
の匿名メソッドが読まれる
↓
binding.output.text = input
でUI更新
流れは上記のようになりますが、、、うーん分かりづらい。
次回は少し発展させて Activity に Fragment 載せた形で実装してみようと思いますが、この文章力で書き続けていいものか不安すぎる(´・ω・`)