2019-09-03 BindingAdapter に (i: Int) -> Unit を渡せない
疲れている。朝は頭痛がひどかったが落ち着いてきた。
Data Binding の Listener のことを書いたつもりになっていたけどメモ止まりだった。 ↓に貼っておく。供養。
(item: T) -> Unit
みたいなのが出てくるとまた動かなくなるように思う。
Data Binding で BindingAdapter に (i: Int) -> Unit
のような Kotlin の関数をうまく渡せない。
↓のような ViewModel があるとする。 Data Binding で click
を呼び出したい。
class MyViewModel : ViewModel() {
fun click(i: Int) {
// do something
}
}
これを簡単に設定するための BindingAdapter を自作しよう。まず動かない例。
@BindingAdapter("onItemSelected")
fun Spinner.setOnItemSelected(listener: ((i: Int) -> Unit)?) {
if (listener == null) return
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
listener(position)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
// do nothing
}
}
}
<Spinner
app:onItemSelected="@{(i) -> viewModel.click(i)}"
...
/>
動きそうな見た目だけど動かない。
@{(i) -> viewModel.click(i)}
の型は Binding Adapter の (i: Int) -> Unit
の型にあってくれない。
[databinding] {"msg":"cannot find method click(java.lang.Object) in class ...
こんなエラーになる。どうも i
が java.lang.Object
(Kotlin の Any
) として扱われている。推論してくれない。
@{viewModel.click}
や @{viewModel::click}
だと……
[databinding] {"msg":"Listener class kotlin.jvm.functions.Function1 with method invoke did not match signature of any method viewModel.click", ...
こんなエラーになる。おそらく Binding Adapter の (i: Int) -> Unit
か ViewModel の click(i: Int): Unit
のどちらかの Function1
から型が落ちてあわないのだろう……。
さて解決策。
まず Binding Adapter と ViewModel を (o: Any) -> Unit
にすれば通るけどダウンキャストが必要になるのであんまりだ。
結局のところ 1 メソッドの interface を定義するのが一番良さそうだった。
interface OnItemSelectedListener {
fun onItemSelected(i: Int)
}
@BindingAdapter("onItemSelected")
fun Spinner.setOnItemSelected(listener: OnItemSelectedListener?) {
if (listener == null) return
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
listener.onItemSelected(position)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
// do nothing
}
}
}
これなら @{(i) -> viewModel.click(i)}
でも @{viewModel.click}
でも @{viewModel::click}
でも interface の型にあってくれる。
ぼくは Java の lambda の挙動に詳しくないのでここからは推測。 Java の lambda はおそらく 1 メソッドの interface を実装する形に変換されるのだろう。そして Data Binding の @{() -> ...}
は Java の lambda に似せているのだろう。そう考えると 1 メソッドの interface を面倒でも定義するのが良い方法なのだと思う。知らんけど。