How to bind method on RadioGroup on checkChanged event with data-binding
Asked Answered
N

5

34

I was searching over the internet for how to perform the new cool android data-binding over the RadioGroup and I didn't find a single blog post about it.

Its a simple scenario, based on the radio button selected, I want to attach a callback event using android data binding. I don't find any method on the xml part which allows me to define a callback.

Like here is my RadioGroup:

      <RadioGroup
            android:id="@+id/split_type_radio"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:checkedButton="@+id/split_type_equal"
            android:gravity="center"
            <!-- which tag ? -->
            android:orientation="horizontal">

           ...

       </RadioGroup>

How do I attach a handler method which will be called on RadioGroup's checkChnged event will fire using data-binding?

I have tried using onClick (don't know if it is the same) in layout file and defining method in the Activity and located it using this in the layout file:

   <variable
        name="handler"
        type="com.example.MainActivity"/>

  ...
   <RadioGroup
        android:onClick="handler.onCustomCheckChanged"
        .. >

And defined method onCustomCheckChanged like this:

public void onCustomCheckChanged(RadioGroup radio, int id) {
     // ...
}

But, it gives me the compilation error:

Error:(58, 36) Listener class android.view.View.OnClickListener with method onClick did not match signature of any method handler.onCustomCheckChanged

I have seen many blogs mentioning it is possible with RadioGroup but non of them really say how. How can I handle this with data-binding ?

Nedrud answered 10/9, 2016 at 14:45 Comment(4)
Have you looked at this answer? I think you can do something similiar. If you still need help. I'll look into it on monday :)Nervy
@Amylinn Thanks for the link. I really appreciate it :) I can try but for what attribute I should do it in xml? Like for checkChanged event in radio button there isn't any attribute which let's me provide a method. While 'onClick' may not work if I pragmatically change the radio button (for example, reset button switch to the default values).Nedrud
I think the corresponding attribute would be app:onCheckedChangeListener.Nervy
@Amylinn Got it! Posted an answer.Nedrud
N
41

After digging to the bunch of methods, I found this question on SO which helped me understand how to bind single methods of listeners.

Here is what to do with RadioGroup:

In RadioGroup listener you have a method onCheckedChanged(RadioGroup g, int id). So you can directly bound that method to your handler or your activity by passing an instance of it as a variable in layout file and calling a method with the same signature.

So call on layout file like this:

  <RadioGroup
        android:id="@+id/split_type_radio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:checkedButton="@+id/split_type_equal"
        android:gravity="center"
        android:onCheckedChanged="@{handler.onSplitTypeChanged}"
        android:orientation="horizontal">

       ...

   </RadioGroup>

And in my activity or handler, I need to simply provide the method with same name and signature like this:

public void onSplitTypeChanged(RadioGroup radioGroup,int id) {
  // ...
}

Just make sure method is public.

NOTE: This works for any (most of, I have not tried all) listener methods. Like for EditText you can provide android:onTextChanged and so on.

Nedrud answered 11/9, 2016 at 14:3 Comment(6)
When i try using the android:onCheckedChanged in my xml it says unknown attribute , i have managed to get data binding to work with on click listener but this same solution isnt working for meDx
@JudeFernandes They have some errors in the lint at present in the android studio, but they compiles fine and works. Just ignore the warning. Can you explain a bit more about your issue?Nedrud
how to handle two way handle through this approach ?Robbynrobe
what if i want name of that checked radio button. ?Interdict
@RayVaniya You have the radio group instance. Get it from that radio groupNedrud
1) it works only View to data and not the other way around. 2) it means adding dependency on View ids - which defeats the purpose of using databinding.Arginine
G
22

I am using a string, and in this case I have bindable based on viewModel.getCommuteType() viewModel.setCommuteType(String)

<RadioGroup
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.DRIVING)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.DRIVING)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="D"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.BICYCLE)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.BICYCLE)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="B"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.WALKING)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.WALKING)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="W"/>

<RadioButton
    android:checked="@{viewModel.commuteType.equals(Commute.BUS)}"
    android:onClick="@{()->viewModel.setCommuteType(Commute.BUS)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="T"/>

Glenoid answered 7/10, 2017 at 5:47 Comment(7)
how to validate it ? is it checked or notSubheading
checkout LiveData.Transformations to benefit from watching another LiveData such as the case of my viewMdodel.commuteType, in this way you can define a LiveData<Boolean> watching over viewModel.commuteType initially having Commute.NONE which produces false, and if it has any other value then it's true. proandroiddev.com/…Glenoid
any time brother. hope that helped you out.Glenoid
used this solution with MediatorLiveData it worked flawlessly.Glochidium
@sud007very nice to hear!Glenoid
Where do you set Commute.DRIVING?Legalese
It's been a while, and I am doing Flutter, but I believe Commute.xxxx were enums based on strings. Good luck!!Glenoid
C
22

After some hours I found easy way: two-way databinding in android. It's base skeleton with and Kotlin. Also you can use ObservableField()

  1. Set your to data
  2. Create your with buttons as you like. Important: set all radio buttons id !!!
  3. Set in your radio group two-way to checked variable (use viewmodel variable)
  4. Enjoy)

layout.xml

<data>
    <variable
        name="VM"
        type="...YourViewModel" />
</data>


<LinearLayout
            android:id="@+id/settings_block_env"
            ...
            >


            <RadioGroup

                android:id="@+id/env_radioGroup"
                android:checkedButton="@={VM.radio_checked}">

                <RadioButton
                    android:id="@+id/your_id1"/>

                <RadioButton
                   android:id="@+id/your_id2" />

                <RadioButton
                    android:id="@+id/your_id3"/>

                <RadioButton
                    android:id="@+id/your_id4"/>
            </RadioGroup>

        </LinearLayout>

class YourViewModel(): ViewModel {

var radio_checked = MutableLiveData<Int>()


init{
    radio_checked.postValue(R.id.your_id1)//def value
}

//other code
}
Cluff answered 18/1, 2019 at 21:42 Comment(11)
This is simplest solution with two way data binding. And you can use default value as constructor param MutableLiveData(R.id.your_id1)Whitesell
Of course , it is. Also instead of var should use val.Cluff
what shall I do with radio_checked.postValue(R.id.your_id1) if they are multiple I want to validate them for example if I have two radio button male and female want to make validation on check@StanislavBondarSubheading
@ELTEGANIMOHAMED I'm not sure, but you should set observers for all LiveData and invoke yourValidate() method. Or create another isValid: LiveData<Boolean>.Cluff
@SeregaMaleev radio_checked.postValue(R.id.your_id1)//def value how would you detect which RadioButton has been clicked ? and how can we get back the value of the radio button that was selected by the user ?Caliphate
android:checkedButtonCluff
@Cosmic Dev You need to set the observer in your fragment or activity.Cluff
This is by far the best and most true to data binding solution for this question. Thank you.Arand
While this works, it means adding dependency on View ids - which defeats the purpose of using databinding.Arginine
So far this is the cleanest and easiest way!Urbane
And this only works in KotlinAlong
T
7

Often you care more about what was actually checked instead of "something was checked". In such case alternative solution is to ignore RadioGroup and bind all items as below:

<RadioGroup (...) >
        <RadioButton (...)
            android:checked="@={viewModel.optionA}"/>

        <RadioButton (...)
            android:checked="@={viewModel.optionB}"/>

        <RadioButton (...)
            android:checked="@={viewModel.optionC}"/>
</RadioGroup>

where optionA, optionB and optionC are defined in ViewModel like below:

public final ObservableBoolean optionA = new ObservableBoolean();
public final ObservableBoolean optionB = new ObservableBoolean();
public final ObservableBoolean optionC = new ObservableBoolean();

This is usually enough, however if you want to react immediately on click then you can add callBacks and use them like that:

OnPropertyChangedCallback userChoosedA = new OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        (...) // basically propertyId can be ignored in such case
    }
};

optionA.addOnPropertyChangedCallback(userChoosedA);

Advantage of such approach is that you don't need to compare and track "id".

Talanta answered 20/12, 2017 at 19:35 Comment(2)
Where does that piece of code go?Along
@Along it really depends but in the simplest possible case "RadioGroup" part goes to xml and two other snippets goes to activity.Talanta
P
5

In my current project, I did it like this. I have three currency in the project and I choose one of them via RadioGroup.

It's enum with currencies:

enum class Currency(val value: Byte) {
    USD(0),
    EUR(1),
    RUB(2);

    companion object Create {
        fun from(sourceValue: Byte): Currency = values().first { it.value == sourceValue }
        fun from(sourceValue: String): Currency = values().first { it.toString() == sourceValue }
    }
}

A piece of my ViewModel:

    class BaseCurrencyViewModel : ViewModelBase<BaseCurrencyModelInterface>() {
        /**
         * Selected currency
         */
        val currency: MutableLiveData<Currency> = MutableLiveData()

        /**
         *
         */
        init {
            currency.value = Currency.USD   // Init value
        }
  }

Part of my layout (pay attention to binding in RadioGroup and tags of RadioButton):

<RadioGroup
    android:id="@+id/currencySwitchers"

    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

    app:selectedCurrency = "@{viewModel.currency}"

    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintEnd_toEndOf="parent">

    <RadioButton
        android:id="@+id/usdSwitcher"

        android:text="USD"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="USD"
    />

    <RadioButton
        android:id="@+id/eurSwitcher"

        android:text="EUR"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="EUR"
    />

    <RadioButton
        android:id="@+id/rubSwitcher"

        android:text="RUB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"

        android:tag="RUB"
    />
</RadioGroup>

And the final part - binding adapter.

@BindingAdapter("selectedCurrency")
fun setSelectedCurrency(view: View, value: MutableLiveData<Currency>?) {
    view.getParentActivity()?.let { parentActivity ->
        value?.observe(parentActivity, Observer { value ->
            view.findViewWithTag<RadioButton>(value.toString())
                ?.also {
                    if(!it.isChecked) {
                        it.isChecked = true
                    }
                }
            }
        )

        (view as RadioGroup).setOnCheckedChangeListener { radioGroup, checkedId ->
            val currency = Currency.from(radioGroup.findViewById<RadioButton>(checkedId).tag as String)
            if(value != null && value.value != currency) {
                value.value = currency
            }
        }
    }
}

In this way, I got two-way binding between RadioGroup and a property in my ViewModel.

Pot answered 12/1, 2019 at 15:44 Comment(3)
do you have your code in android - java and not Kotlin?Kinard
@Kinard No, sorry. I don't use Java recently.Pot
@AlexShevelev Thank you...for the solutionBaucis

© 2022 - 2024 — McMap. All rights reserved.