androidkotlindata-bindingparameter-passingandroid-binding-adapter

Android custom view using custom binding adapter listener method with parameter


I'm developing a custom search view and I need to add a listener so the viewmodel can perform the search with the query using databinding, I'm currently having issues setting up the binding adapter with the query parameter, here are the relevant parts:

This is the custom view with the listener event:

class MySearchView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.editTextStyle
) : AppCompatEditText(context, attrs, defStyleAttr) {

    var onSearchListener: ((query: String) -> Unit)? = null
}

This is the binding adapter:

@BindingAdapter("onSearchListener")
fun MySearchView.setOnSearchListener(event: (query: String) -> Unit) {
    onSearchListener = event
}

This is the XMl layout part:

<com.xxx.MySearchView
    android:id="@+id/txt_search"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:hint="@string/label_search"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:onSearchListener="@{(query) -> viewModel.onSearchTriggered(query)}"/>

Finally my method in the viewmodel:

fun onSearchTriggered(query: String) {
    Log.d("search", "search: $query")
}

When I compile the XML fails telling me that it cannot find the method above, here is the error:

cannot find method onSearchTriggered(java.lang.Object) in class com.xxx.MyViewModel

I've tried before with custom listeners without parameters and everything worked just fine so I assume I must be doing something wrong when adding the parameter, any ideas?


Solution

  • I do not think that would work as the type of parameter cannot be specified in XML, but I was hoping passing viewModel::onSearchTriggered would work but it looks like it does not. You could use one of the following to do this:

    1. You can declare a variable instead of a function in the ViewModel:
    val onSearchTriggered = { query: String ->
        Log.d("search", "search: $query")
    }
    

    You can then directly pass the variable like so:

    <com.xxx.MySearchView
        android:id="@+id/txt_search"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/label_search"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:onSearchListener="@{viewModel.onSearchTriggered}"/>
    
    1. You could create an interface for your listener:
    interface OnSearchListener {
        fun onSearch(String)
    }
    
    @BindingAdapter("onSearchListener")
    fun MySearchView.setOnSearchListener(listener: OnSearchListener) {
        onSearchListener = listener::onSearch
    }
    

    You can then use it like so:

    <com.xxx.MySearchView
        android:id="@+id/txt_search"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/label_search"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:onSearchListener="@{viewModel::onSearchTriggered}"/>