6

I would like to create ChipGroup 2-way binding adapter. I have copied default RadioGroup binding adapter with some changes but it doesn't work for both ways. In case of setting data to observable programmatically, ChipGroup retrieve changes from it. But manually Chip selection doesn't set changes to observable.

Here is my adapter

@InverseBindingMethods(InverseBindingMethod(type = ChipGroup::class, attribute = "android:checkedButton", method = "getCheckedRadioButtonId"))
class ChipGroupBindingAdapter {
companion object {
    @JvmStatic
    @BindingAdapter("android:checkedButton")
    fun setCheckedChip(view: ChipGroup?, id: Int) {
        if (id != view?.checkedChipId) {
            view?.check(id)
        }
    }

    @JvmStatic
    @BindingAdapter(value = ["android:onCheckedChanged", "android:checkedButtonAttrChanged"], requireAll = false)
    fun setChipsListeners(view: ChipGroup?, listener: ChipGroup.OnCheckedChangeListener?,
                          attrChange: InverseBindingListener?) {
        if (attrChange == null) {
            view?.setOnCheckedChangeListener(listener)
        } else {
            view?.setOnCheckedChangeListener { group, checkedId ->
                listener?.onCheckedChanged(group, checkedId)
                attrChange.onChange()
                }
            }
        }
    }
}

Layout file:

<android.support.design.chip.ChipGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@{viewModel.checkedBtnObs}"
        app:singleSelection="true">

        <android.support.design.chip.Chip
            android:id="@+id/first_chip"
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="110dp"
            android:layout_height="wrap_content"
            android:checkable="true"
            android:text="@string/month_12"
            app:chipBackgroundColor="@drawable/chip_background_selector" />

        <android.support.design.chip.Chip
            android:id="@+id/second_chip"
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="110dp"
            android:layout_height="wrap_content"
            android:checkable="true"
            android:text="@string/month_6"
            android:textAlignment="center"
            app:chipBackgroundColor="@drawable/chip_background_selector" />

        <android.support.design.chip.Chip
            android:id="@+id/third_chip"
            style="@style/Widget.MaterialComponents.Chip.Choice"
            android:layout_width="110dp"
            android:layout_height="wrap_content"
            android:checkable="true"
            android:text="@string/month_1"
            app:chipBackgroundColor="@drawable/chip_background_selector" />

    </android.support.design.chip.ChipGroup>

And observable:

val checkedBtnObs = ObservableInt(R.id.second_chip)
Mikhail Sharin
  • 3,661
  • 3
  • 27
  • 36

3 Answers3

7

Finally, I found solution. InverseBindingMethod method should be getCheckedChipId instead of getCheckedRadioButtonId

Also, @= should be added to xml android:checkedButton="@{viewModel.checkedBtnObs}" like this android:checkedButton="@={viewModel.checkedBtnObs}"

Now this adapter can be used for ChipGroup 2-way binding

@InverseBindingMethods(InverseBindingMethod(type = ChipGroup::class, attribute = "android:checkedButton", method = "getCheckedChipId"))
class ChipGroupBindingAdapter {
companion object {
    @JvmStatic
    @BindingAdapter("android:checkedButton")
    fun setCheckedChip(view: ChipGroup?, id: Int) {
        if (id != view?.checkedChipId) {
            view?.check(id)
        }
    }

    @JvmStatic
    @BindingAdapter(value = ["android:onCheckedChanged", "android:checkedButtonAttrChanged"], requireAll = false)
    fun setChipsListeners(view: ChipGroup?, listener: ChipGroup.OnCheckedChangeListener?,
                          attrChange: InverseBindingListener?) {
        if (attrChange == null) {
            view?.setOnCheckedChangeListener(listener)
        } else {
            view?.setOnCheckedChangeListener { group, checkedId ->
                listener?.onCheckedChanged(group, checkedId)
                attrChange.onChange()
                }
            }
        }
    }
}
Mikhail Sharin
  • 3,661
  • 3
  • 27
  • 36
2

I got ChipGroup with DataBinding working as expected and using the recommended way of InverseDataBinding, here's my implementation if anyone wish to know:

SomeBindingAdapters.kt:

...
object PropertyTypeFilterBindingAdapters {
    @BindingAdapter("propertyTypeFilter")
    @JvmStatic
    fun ChipGroup.bindPropertyTypeFilter(marsApiFilter: MarsApiFilter?) =
        marsApiFilter?.let { filter ->
            when (filter) {
                MarsApiFilter.ALL -> check(R.id.filter_all_properties_chip)
                MarsApiFilter.RENT -> check(R.id.filter_properties_for_rent_chip)
                MarsApiFilter.BUY -> check(R.id.filter_properties_for_buy_chip)
            }
        }

    @InverseBindingAdapter(attribute = "propertyTypeFilter")
    @JvmStatic
    fun ChipGroup.convertToMarsApiFilter(): MarsApiFilter = when (checkedChipId) {
        R.id.filter_properties_for_rent_chip -> MarsApiFilter.RENT
        R.id.filter_properties_for_buy_chip -> MarsApiFilter.BUY
        else -> MarsApiFilter.ALL
    }

    @BindingAdapter("propertyTypeFilterAttrChanged")
    @JvmStatic
    fun ChipGroup.setListeners(attrChange: InverseBindingListener?) =
        setOnCheckedChangeListener { _, _ -> attrChange?.onChange() }
}
...
  • MarsApiFilter is a enum class for my specific case.

some_layout.xml:

...
<com.google.android.material.chip.ChipGroup
    android:id="@+id/filter_property_type_chip_group"
    ...
    bind:propertyTypeFilter="@={viewModel.propertyTypeFilter}"
    >

    <com.google.android.material.chip.Chip
        android:id="@+id/filter_all_properties_chip"
        ...
        />

    <com.google.android.material.chip.Chip
        android:id="@+id/filter_properties_for_rent_chip"
        ...
        />

    <com.google.android.material.chip.Chip
        android:id="@+id/filter_properties_for_buy_chip"
        ...
        />
</com.google.android.material.chip.ChipGroup>
...
  • bind is just a fancy namespace declared as a regular one: xmlns:bind="http://schemas.android.com/apk/res-auto"

SomeViewModel.kt:

...
val propertyTypeFilter = MutableLiveData<MarsApiFilter>().apply { value = MarsApiFilter.ALL }
...
Filipe Bezerra de Sousa
  • 2,874
  • 1
  • 17
  • 17
  • if you want use setOnCheckedChangeListener() of ChipGroup than you need to make app:singleSelection="true" important!!! – Hasan Kucuk Oct 15 '21 at 17:03
0
if (attrChange == null) {
            view?.setOnCheckedChangeListener(listener)
        } else {
            view?.setOnCheckedChangeListener { group, checkedId ->
                listener?.onCheckedChanged(group, checkedId)
                attrChange.onChange()
                }
            }
        }

maybe should be

if (listener != null) {
            view?.setOnCheckedChangeListener(listener)
        } else {
            view?.setOnCheckedChangeListener { group, checkedId ->
                listener?.onCheckedChanged(group, checkedId)
                attrChange.onChange()
                }
            }
        }
王世伟
  • 11
  • 1