Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ChipGroup 2-way binding adapter

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)
like image 431
Mikhail Sharin Avatar asked Sep 14 '18 09:09

Mikhail Sharin


2 Answers

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()
                }
            }
        }
    }
}
like image 195
Mikhail Sharin Avatar answered Oct 19 '22 16:10

Mikhail Sharin


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 }
...
like image 25
Filipe Bezerra de Sousa Avatar answered Oct 19 '22 18:10

Filipe Bezerra de Sousa