AnkoとData Bindingを同時に使えない
AnkoとData Bindingは同時に使うことができません。
DataBindingではXMLの名称から ~~Binding
という名称のクラス(仮にBindingクラスと呼ぶことにします)を自動的に生成してくれますが、Ankoでレイアウトを作成した場合にはBindingクラスを生成してくれません。
Data Bindingのように、Modelを変更したときにViewを更新してくれるようにしたいときにはどうすれば良いのでしょうか?
Data Bindingの完全な代替にはなりませんが、委譲プロパティを使用すればこのようなときに期待の動作をさせることができます。
委譲プロパティ
委譲プロパティ(Delegated Properties)を利用すると、あるプロパティにアクセスする際にログを出したりなどといった特定の処理を挟むことができます。
プロパティの後ろに by ~
と書くことで 特定のオブジェクトに処理を委譲できます。
委譲先のクラスとして下記のようなクラスを作成します。
class Counter() : ReadWriteProperty<Any, Int> {
private var mValue: Int? = null
override fun getValue(thisRef: Any, property: KProperty<*>): Int {
return mValue as Int
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
Log.d("mydebug", "${property.name}: $value")
mValue = value
}
}
下記のようにby Counter()
とすればcountへの代入時にCounterクラスのsetValue関数が呼ばれます。Counter()
は単なるCounterクラスのコンストラクタです。
var count: Int by Counter()
// 省略
count = 1
count++
count = 10
count: 1
count: 2
count: 10
同じように、プロパティの値を取得するときにはgetValue関数が呼び出されます。
下のようにCounterクラスのコンストラクタに値や関数を渡すこともできます。
class Counter<T>(val function: (property: KProperty<*>, T) -> Unit) : ReadWriteProperty<Any, T> {
private var mValue: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T {
return mValue as T
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
mValue = value
function(property, value)
}
}
var count: Int by Counter { property, value ->
Log.d("mydebug", "${property.name}: $value")
}
なんとかする
以下のように委譲プロパティを使えば、Modelを変更したときにViewを更新する一方向バインディングができます。
class ViewBinder<M>(val function: (M) -> Unit) : ReadWriteProperty<Any, M> {
private var mValue: M? = null
override fun getValue(thisRef: Any, property: KProperty<*>): M {
return mValue as M
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: M) {
if (mValue == value) {
return
}
mValue = value
function(value)
}
}
class MyActivityUI() :AnkoComponent<MainActivity>() {
var user: User? by ViewBinder {
textView.text = user?.name
// 省略
}
// 省略
}