完全に自分用メモなので乱文です。
MVVM とは
Model-View-ViewModel なアーキテクチャ。
View(画面)は、ViewModel のプロパティ(Android-binding では IObservable)の変更を検知し、コントロール(Android ではウィジェット)の内容を置き換える。(OneWay の場合)
TwoWay の場合、ユーザーの操作によってコントロールの値が変化した時、ViewModel のプロパティにその変更を適用する。
超断片的なので、詳しくはおググりください。
例:
EditText の Text と、ViewModel の Address プロパティが関連付け(バインド)されていた場合、
- ViewModel.Address がプログラム処理によって変更された時に、EditText.Text が自動的に変更されるのが OneWay。
- 上記に加え、EditText にユーザーが文字を入力した時に、ViewModel.Address にその値が適用されるのが、TwoWay。
Android-Binding とは
Android で MVVM できるライブラリ。
何が知りたいのか?
TwoWay の時、EditText にユーザーが文字列を入力して、ViewModel.Address に新しい値を設定する。すると、その変更をまた View が検知して、EditText の値を書き換える処理が動いてしまう(OneWay 側の動きをする)のではないか?
通常の文字列などでは、EditText に同じ値が再設定されるだけなので問題なさげだが、それでも意図しないUI更新は気持ち悪い。
調べてみる
Android-Binding では、この辺りを上手く処理しているように見えるので、実装を見てみる。
Android-Binding で、ViewModel 側のプロパティを "Property"、それをバインドする View 側の定義を "Attribute" という。というか勝手にそう呼ぶことにする。(実体はどちらも IObservable の実装だが、この辺を説明しだすと混乱するので割愛)
前述の例だと、「EditText の Text」が "Attribute"、ViewModel.Address が "Property" になる。
-
Attribute に Property がバインドされると、
Attribute#onBind
が呼び出され、この中でAttribute#Bridge
クラス(これは Observer である)が生成され、Bridge が Property の変更を監視する。と同時にthis.subscribe(mBridge)
にて TextViewAttribute 自身の変更も監視する。[github] -
[TextViewAttribute]ユーザーが EditText の Text を変更すると、
TextViewAttribute#onTextChanged
が呼び出され、さらにnotifyChanged
が呼び出される。[github] -
[TextViewAttribute]notifyChanged() は Attribute の基底クラスである
Observable#notifyChanged()
が呼び出され、続いてオーバーロードであるnotifyChanged(initiators)
が呼ばれる。 [github] -
[TextViewAttribute]
initiators
とは、.NET イベントの sender のようなもので、イベント(コールバック)発生元のオブジェクトを示す。ただし、Observable#notifyChanged()
の時点では、initiators はまだ空っぽ。 -
[TextViewAttribute]
notifyChanged(Collection<Object> initiators)
にて、initiators にthis
つまり TextViewAttribute 自身を追加して、監視している Observer のonPropertyChanged
を呼び出す。[github] -
[TextViewAttribute]TextViewAttribute の監視者は
Attribute#Bridge
クラスであり、Bridge#onPropertyChanged(prop, initiators)
が呼び出される。この時点で、prop
は TextViewAttribute 自身、initiators にも TextViewAttribute 自身が格納されている。[github] -
[TextViewAttribute]
Bridge#onPropertyChanged
の最初の if文if (prop==mAttribute){
が true となり、mBindedObservable._setObject(prop.get(), initiators);
が呼び出される。mAttribute
は TextViewAttribute 自身であり、mBindedObservable
はバインドした ViewModel の Property である。[github] -
[Property]呼び出されたのは、ViewModel の Property(Observable)、つまり
Observable#_setObject(newValue, initiators)
である。initiators には依然として TextViewAttbute が1件格納されている。ここからObservable#set(newValue, initiators)
が呼び出される。[github] -
[Property]
Observable#set(newValue, initiators)
では、doSetValue
で、自身(ViewModel の Property)の値を書き換え、その後、initiators に this、つまり ViewModel の Property を追加してnotifyChanged
を呼び出す。この時点で initiators は 2件(TextViewAttribute と ViewModelのProperty) である。[github] -
[Property]
notifyChanged(Collection<Object> initiators)
にて、initiators に更に this を追加して(この処理に意味があるのかは謎)、監視者である Observer のonPropertyChanged(this, initiators)
を呼び出す。この時点で initiators は 3件(TextViewAttribute と ViewModelのProperty と ViewModelのProperty) [github] -
[TextViewAttribute]再び Bridge#onPropertyChanged(prop, initiators) が呼び出される。ここでは prop は ViewModelのProperty である。これにより、最初の if文 は
false
となる。[github] -
[TextViewAttribute]次の if文
else if (prop==mBindedObservable){
はtrue
となるが、更にその次の if文if (initiators.contains(Attribute.this))
は、initiators に TextViewAttribute が含まれている為 false となり、処理は終わる。これにより、次の行にあるmAttribute._setObject(prop.get(), initiators);
は呼び出されない、つまり、EditText の Text に値が再設定されることはない。[github]
まとめ
- Binder の実装は、Bridge が EditText と ViewModelのProperty 両方の変更を監視し、また initiators という仕組みをうまく使うことで、EdtiText が起こした Property の変更は、Attribute が検知しないように工夫されていた。
Android-Binding の仕組みはなんとかわかったー。.NET の XAML はどうやってんのかな?