This is my BindingAdapter
:
@BindingAdapter(value = *arrayOf("bind:commentsAdapter", "bind:itemClick", "bind:avatarClick", "bind:scrolledUp"), requireAll = false)
fun initWithCommentsAdapter(recyclerView: RecyclerView, commentsAdapter: CommentsAdapter,
itemClick: (item: EntityCommentItem) -> Unit,
avatarClick: ((item: EntityCommentItem) -> Unit)?,
scrolledUp: (() -> Unit)?) {
//Some code here
}
initWithCommentsAdapter
is a top level function
This is my layout (an essential part):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="some.example.path.CommentsViewModel"/>
<variable
name="commentsAdapter"
type="some.example.path.CommentsAdapter"/>
</data>
<android.support.v7.widget.RecyclerView
...
bind:avatarClick="@{(item) -> viewModel.avatarClick(item)}"
bind:itemClick="@{viewModel::commentClick}"
bind:commentsAdapter="@{commentsAdapter}"
bind:isVisible="@{viewModel.commentsVisibility}"
bind:scrolledUp="@{() -> viewModel.scrolledUp()}"
/>
</layout>
When I assign lambda with kotlin method call in the layout, I have such error during building:
e: java.lang.IllegalStateException: failed to analyze:
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:cannot find method avatarClick(java.lang.Object)
in class some.example.path.CommentsViewModel
****\ data binding error ****
or if I assign method by reference:
e: java.lang.IllegalStateException: failed to analyze:
java.lang.RuntimeException: Found data binding errors.
****/ data binding error ****msg:Listener class kotlin.jvm.functions.Function1
with method invoke did not match signature of any method viewModel::commentClick
file:C:\Android\Projects\...\fragment_comments.xml
loc:70:12 - 83:17
****\ data binding error ****
But I have such methods with proper type, not Object
Question
How can I assign Kotlin lambda for custom @BindingAdapter in Kotlin in the layout?
Edit
The relevant part of the viewModel:
class CommentsViewModel(model: CommentsModel): BaseObservable() {
//Some binded variables here
...
fun commentClick(item: EntityCommentItem) {
//Some code here
}
fun avatarClick(item: EntityCommentItem) {
//Some code here
}
fun scrolledUp() {
//Some code here
}
...
}
The variables binding works just fine
Short Answer
Instead of using Kotlin generic lambda types, use interfaces with a single method that matches both return type and parameters of your method reference (itemClick
) or your listener (avatarClick
).
You can also use abstract classes with a single abstract method, also with matching parameters and return type.
Explanation
Actually the Databinding docs never mention that the Kotlin lambda types work as Databinding listeners or method references, probably because under the hood these lambda types translate to Kotlin's Function1
, Function2
... which are generics, and thus some of their type information doesn't make it to the executable and therefore is not available at runtime.
Why your scrolledUp
binding did work though? Because type () -> Unit
has no need for generics. It could have worked even with Runnable
.
Code
interface ItemClickInterface {
// method may have any name
fun doIt(item: EntityCommentItem)
}
@BindingAdapter(
value = ["commentsAdapter", "scrolledUp", "itemClick", "avatarClick"],
requireAll = false
)
fun initWithCommentsAdapter(
view: View,
commentsAdapter: CommentsAdapter,
scrolledUp: () -> Unit, // could have been Runnable!
itemClick: ItemClickInterface,
avatarClick: ItemClickInterface
) {
// Some code here
}
I ran into the same case, and what worked was having it declared as variable defining its type, that worked with the compiler
val avatarClick:(item: EntityCommentItem)->Unit = {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With