Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android DataBinding: @BindingAdapter in Kotlin does not recognize lambdas

Tags:

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

like image 957
Владимир Широков Avatar asked Oct 27 '17 09:10

Владимир Широков


2 Answers

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
}
like image 130
Leo supports Monica Cellio Avatar answered Oct 03 '22 08:10

Leo supports Monica Cellio


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 = {}

like image 31
Juan Mendez Avatar answered Oct 03 '22 10:10

Juan Mendez