android data binding with a custom view

Solution 1:

In your Custom View, inflate layout however you normally would and provide a setter for the attribute you want to set:

private MyCustomViewBinding mBinding;
public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

public void setMyViewModel(MyViewModelObject obj) {
    mBinding.setMyViewModel(obj);
}

Then in the layout you use it in:

<layout xmlns...>
    <data>
        <variable
            name="myViewModel"
            type="com.mypath.MyViewModelObject" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.mypath.MyCustomView
            android:id="@+id/my_view"
            app:myViewModel="@{myViewModel}"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

    </LinearLayout>
</layout>

In the above, an automatic binding attribute is created for app:myViewModel because there is a setter with the name setMyViewModel.

Solution 2:

First, don't do this if this custom view is already being <include> in another layout, such as activity etc. You'll just get an exception about the tag being unexpected value. The data binding already ran the binding on it, so you're set.

Did you try using onFinishInflate to run the bind? (Kotlin example)

override fun onFinishInflate() {
    super.onFinishInflate()
    this.dataBinding = MyCustomBinding.bind(this)
}

Keep in mind that if you use the binding in your view, it won't be able to be created programmatically, at least it would be very convoluted to support both even if you can.

Solution 3:

Data binding works even with merge only parent had to be "this" and attach to parent true.

binding = DataBindingUtil.inflate(inflater, R.layout.view_toolbar, this, true)

Solution 4:

Following the solution presented by george the graphical editor in android studio was no longer able to render the custom view. The reason is, that no view is actually inflated in the following code:

public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

I suppose that the binding handles the inflation, however the graphical editor did not like it.

In my specific use case I wanted to bind a single field and not an entire view model. I came up with (kotlin incoming):

class LikeButton @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    val layout: ConstraintLayout = LayoutInflater.from(context).inflate(R.layout.like_button, this, true) as ConstraintLayout

    var numberOfLikes: Int = 0
      set(value) {
          field = value
          layout.number_of_likes_tv.text = numberOfLikes.toString()
      }
}

The like button consists of an image and a text view. The text view holds the number of likes, which I want to set via data binding.

By using the setter for numberOfLikes as an attribute in the following xml, data binding automatically makes the association:

<views.LikeButton
  android:id="@+id/like_btn"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:numberOfLikes="@{story.numberOfLikes}" />

Further reading: https://medium.com/google-developers/android-data-binding-custom-setters-55a25a7aea47