4

I'm trying to use Kotlin extension methods inside Android's databinding. For example; calling an onclick handler. So I've made this code:

posttest_list_item.xml

<?xml version="1.0" encoding="utf-8"?>

<data>
    <import type="android.view.View"/>
    <import type="com.example.test.post.posttest.PostTestItemViewModelExtensionKt" />
    <variable
        name="viewModel"
        type="com.example.test.post.posttest.PostTestItemViewModel" />
</data>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:clickable="true"
    android:onClick="@{(view) -> viewModel.clicked(view)}"
    >
[...]

PostTestItemViewModel.kt

open class PostTestItemViewModel : ViewModel() {
    val postTitle = MutableLiveData<String>()
    val postBody = MutableLiveData<String>()

   /**
    * Binds the required properties/entities to this ViewModel
    */
   fun bind(post: Post) {
       postTitle.value = post.title
       postBody.value = post.body
   }
}

PostTestItemViewModelExtension.kt

fun PostTestItemViewModel.clicked(v: View) {
    this.postTitle.value = "clicked"
}

So when I place the clicked method inside the viewmodel, it works perfectly the way it should be. However, when I create it as an extension method, I get the following error on compilation:

e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding errors. cannot find method clicked(android.view.View) in class ...PostItemViewModel

I've tried different things already, such as changing the android:onclick tag to PostTestItemViewModelExtensionKt instead of viewModel. Unfortunately all the things don't seem to work. So it looks like the extension method is getting generated after the databinding takes place. Is there a way around this or am I still doing something wrong? Or is it just not possible to bind extension methods?

I'm using Kotlin version 1.2.71, gradle 3.2.0 and have the databinding { enabled = true } and kapt { generateStubs = true } added to my .gradle, and have the plugings kotlin-android, kotlin-android-extensions and kotlin-kapt defined.

4
  • Java (i.e. the Android Framework) can't access extension methods afaik, so you'll need to put the clicked method in the class itself Commented Oct 1, 2018 at 10:43
  • 2
    @zapl Java can access extension methods, but in form PostTestItemViewModelExtensionKt.clicked(postTestItemViewModelInstance). Extension methods are static methods for Java. Commented Oct 1, 2018 at 12:03
  • 1
    So actually android:onClick="@{(view) -> PostTestItemViewModelExtensionKt.clicked(viewModel, view)}" could work? Commented Oct 1, 2018 at 14:30
  • look at this question: stackoverflow.com/questions/47868672/… Commented Oct 1, 2018 at 14:42

2 Answers 2

5

Unfortunately you can't use extension methods as onClick callbacks.

Extension methods in Kotlin are created as Java static methods while the Android framework is expecting an instance method.

Note that in Android Studio you can decompile the Kotlin classes as Java to see the generated Java code.

Sign up to request clarification or add additional context in comments.

Comments

0

So, today(2022) I had the same use case in one of my projects and i was able to figure out a way to implement custom click listeners for android views using data binding and custom adapters.

The use case is :

Click event should not be triggered twice or to prevent accidental clicks from the user

I created a file called ViewExtensions.kt and added the following code

 class DebouncingOnClickListener(
      private val intervalMillis: Long,
      private val doClick: (() -> Unit)
    ) : View.OnClickListener {

    override fun onClick(v: View) {
        if (enabled) {
            enabled = false
            v.postDelayed(ENABLE_AGAIN, intervalMillis)
            doClick()
        }
    }

    companion object {
        @JvmStatic
        var enabled = true
        private val ENABLE_AGAIN =
            Runnable { enabled = true }
    }
}

@BindingAdapter("singleClick")
fun View.setSingleClick(doClick: () -> Unit) =
    setOnClickListener(
        DebouncingOnClickListener( 
            intervalMillis = 5000, //5ms delay for click event
            doClick = doClick
        )
    )

The debouncing click is used to defer the click for the given time, and in the xml called the click event like below

 <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        app:singleClick="@{()->fragment.clicked()}" />

Now I'm able to listen for click events on both fragment and in the viewmodel and the click is deferred for the given amount of time.

Hence the user cannot click the view accidentally multiple times.

References: https://proandroiddev.com/ensure-single-click-on-android-butterknife-did-it-right-48ef56153c78

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.