16

I am running into issues trying to take a function as a parameter in a binding adapter using Kotlin/Android databinding. This example code throws e: error: cannot generate view binders java.lang.StackOverflowError when building with no other useful info in the log.

Here is my binding adapter:

@JvmStatic
@BindingAdapter("onDelayedClick")
fun onDelayedClick(view: View, function: () -> Unit) {
    // TODO: Do something
}

XML:

        <View
            android:id="@+id/test_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:onDelayedClick="@{() -> viewModel.testFunction()}"/>

and method in my ViewModel:

fun testFunction() = Unit

I have been struggling with this for a bit now and nothing I've tried works, so any help is appreciated.

Eric Bachhuber
  • 3,872
  • 2
  • 19
  • 26

4 Answers4

15

Use function: Runnable instead of function: () -> Unit.

Android's data-binding compiler generates java code, to which, your kotlin function's signature looks like void testFunction(), as kotlin adapts Unit as void when calling from java.

On the other hand, () -> Unit looks like kotlin.jvm.functions.Function0 which is a function which takes 0 inputs and returns Unit.INSTANCE.

As you can see these two function signatures don't match, and that's why the compilation fails.

Vibin
  • 843
  • 6
  • 18
1

Put apply plugin: 'kotlin-kapt' in build.gradle

Then you can create Binding Adapter like

@JvmStatic
@BindingAdapter("onDelayedClick")
fun onDelayedClick(view: View, function: () -> Unit) {
    // TODO: Do something
}

And XML Like

<View
   android:id="@+id/test_view"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:onDelayedClick="@{viewModel.testFunction}"/>

And VM Like

val testFunction =  {
    // TODO: Do something
}
Shashank Kumar
  • 1,210
  • 10
  • 18
0

In the Event Handling section I came across this line:

In Listener Bindings, only your return value must match the expected return value of the listener (unless it is expecting void)

For more about error :

cannot generate view binders java.lang.StackOverflowError

read this article. hope it will help you!!

Hemant Parmar
  • 3,924
  • 7
  • 25
  • 49
-1

The declaration () -> Unit suggests a function which takes no input and returns nothing (Unit is the return type in this statement). Your function should look like this:

fun testFunction() = {}
Sir Codesalot
  • 7,045
  • 2
  • 50
  • 56
  • The databinding compiler throws the same exception (cannot generate view binders java.lang.StackOverflowError) if I change the `Unit` return to `{}`. Just so you are aware, returning Unit is exactly the same as returning an empty block, you can check the Kotlin bytecode yourself to see that this is the case. I had just written it like that to keep the example as simple as possible. – Eric Bachhuber Apr 15 '18 at 22:06
  • @EricBachhuber Did you find a fix ? Because I saw other post saying that maybe a possible bug of kotlin / databinding. – Dex Sebas Apr 18 '18 at 18:29
  • @Dex Sebas no fix that I know of, as far as I know it's a bug in the databinding compiler. The workaround I'm using is to use a different return type in the parameter (i.e. `function: () -> Boolean` instead of `function: () -> Unit`), but it's hacky and requires changing the return type of the function that is being called which is not always possible (and poor design, imo). – Eric Bachhuber Apr 18 '18 at 19:43
  • And in your layout / kt file class you're binding like this ? "@{viewModel.itemClick()}" or "@{() -> viewModel.itemClick()}" /// class fun itemClick() : Boolean { Log.e("test", "test") return true } – Dex Sebas Apr 18 '18 at 19:45
  • I've tried both, but neither work. I opened an issue https://issuetracker.google.com/issues/78197871 – Eric Bachhuber Apr 18 '18 at 19:51
  • @Dex Sebas sorry, I misread your comment. For the workaround, parameter `function: () -> Boolean` `@{() -> viewModel.itemClick()}` and then `fun itemClick(): Boolean { return true }` would work fine (compiles without StackOverflowError and functions properly in the app) – Eric Bachhuber Apr 18 '18 at 20:02
  • Yes, I did fun itemClick() : Boolean { Log.e("test", "test") return true } but its not compiling. I added the code in the issueTracker of google. – Dex Sebas Apr 18 '18 at 20:04
  • Okay, I have not used multiple binding parameters (i.e. @BindingAdapter("adapter", "action") so cannot speak to that. But with just one parameter (i.e. `function () -> Boolean`) it works fine. Do you get a StackOverflowException as well? – Eric Bachhuber Apr 18 '18 at 20:08
  • Yes, seems like a bug maybe? Because it not works with multiple binding parameter. I just saw after a invalidate cache a error 'Cannot find the setter for attribute 'app:action' with parameter type lambda' but with only one parameter works fine (using Boolean and not Unit). Sad, it breaks my idea of making a masteradapter for recyclerView – Dex Sebas Apr 18 '18 at 20:10