fweigl
fweigl

Reputation: 22038

BindingAdapter with lambda as argument

With Android databinding, I can do the following:

<View
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:onClick="@{() -> viewModel.onClick()}" />

My ViewModel does not have to implement OnClickListener, but just have a method:

public void onClick() {

}

What it passes to the onClick attribute in the Xml looks like a lambda to me.

How can I do this with my own BindingAdapters?

What I want: Let's assume I want to bind touch events and I want to pass the MotionEvent, I would imagine this to look in the Xml like this:

<View
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:onTouch="(view, event) -> viewModel.onTouch(event)" />

and the BindingAdapter something like:

@BindingAdapter("onTouch")
public static void onTouch(View view, ??? lambda) {
  view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(final View v, final MotionEvent event) {
      return lambda(event);
    }
  });
}

I do not want my ViewModel to implement OnTouchListener and bind it like:

@BindingAdapter()
public  static  void onTouch(View view, View.OnTouchListener onTouchListener) {
    view.setOnTouchListener(onTouchListener);
}

and I do not want bind the touch event directly to my ViewModel like:

@BindingAdapter()
public static void onTouch(final View view, final MyViewModel myViewModel) {
  view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(final View v, final MotionEvent event) {
      return myViewModel.onTouch(view, myViewModel);
    }
  });
}

Is this possible with Databinding?

Upvotes: 6

Views: 2891

Answers (3)

Oya Canli
Oya Canli

Reputation: 2516

As you want it to work like the built-in onClick, let's take a look at the official binding adapter for onClick attribute:

@BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {
    view.setOnClickListener(clickListener);
    view.setClickable(clickable);
}

As you can see, the binding adapter doesn't take a lambda as argument, it takes a listener. But you can pass a lambda to it in xml (since it is a single method interface)

So here is the binding adapter you need: (I chose another attribute name, since onTouch is already used by framework)

@BindingAdapter("onImageTouch")
public static void onImageTouch(View view, View.OnTouchListener onTouchListener) {
    view.setOnTouchListener(onTouchListener);
}

This is how you use it in xml:

app:onImageTouch="@{(view, event) -> viewModel.onImageTouch(event)}"

And here is the corresponding method in viewmodel:

public boolean onImageTouch(MotionEvent event){
    Timber.e("OnImageTouch is called");
    return true;
}

(It does nothing, but notice that it should return a boolean because onTouch callback returns a boolean.)

You don't need to implement OnTouchListener in your viewmodel or activity. This works very similar to the example with onClick that you gave in the beginning.

Let me note that there is already a built-in binding adapter for onTouch attribute, but I guess it was just an example and you in fact want to learn how to make custom ones. But be careful not to choose attribute names that already exist in the framework, to avoid any clashes.

Upvotes: 10

Mateo
Mateo

Reputation: 559

You can use a lambda with your own method if it returns a boolean, and use the attribute app:onTouchListener. No custom BindingAdapter needed.

<View
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:onTouchListener="(view, event) -> viewModel.onTouch(event)" />

android:onTouch also works but you get an Unknown attribute lint warning

Upvotes: 3

pratham kesarkar
pratham kesarkar

Reputation: 3818

It is possible to pass data to your view model however you also have to pass view along with data. so in your case. what you need to do is as follow.

<View
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:onClick="@{() -> viewModel.onClick(view.someData)}" />

And in your View Model.

public void onClick(View v,String someData) {
}

Upvotes: -1

Related Questions