Search code examples
androidkotlinandroid-widgetoncheckedchanged

Toggle one button and untoggled other button


I'm learning Kotlin and I'm trying to make two buttons that can be toggled with the following characteristics:

  1. Button 1 starts as toggled(true) and button 2 starts as untoggled(false).
  2. If I click on the button that is untoggled, he becomes toggled and the other is untoggled.
  3. If I click on the button that is toggled, he becomes untoggled and the other is toggled.
  4. If one button is true the other is false.
  5. The toggled button is always yellow and the other untoggled is white.

The buttons code:

<ToggleButton
    android:id="@+id/button1"
    android:background="@color/yellow"
    android:text="ToggleButton"
    android:textOff="Ton"
    android:textOn="Ton"/>

<ToggleButton
    android:id="@+id/button2"
    android:background="@color/white"
    android:text="ToggleButton"
    android:textOff="Kg"
    android:textOn="Kg"/>

The activity code:

private val btn1 by lazy { findViewById<View>(R.id.button1) as ToggleButton }
private val btn2 by lazy { findViewById<View>(R.id.button2) as ToggleButton }

btn1.setOnClickListener{
    btn1.setOnCheckedChangeListener { _, isChecked ->
        if (isChecked) { //if btn1 is true he is yellow and btn2 is false and he is white
            btn2.isChecked = false
            btn1.setBackgroundColor(Color.YELLOW)
            btnLoadModeFrac.setBackgroundColor(Color.WHITE)
        } else { //if btn1 is false he is white and btn2 is true and he is yellow
            btn2.isChecked = true
            btn1.setBackgroundColor(Color.WHITE)
            btn2.setBackgroundColor(Color.YELLOW)
        }
    }
}

PROBLEM

The problem here is that, on the first click, I can toggle both button 1 and button 2 (Both are true), even when I set one as false and the other one as true. After that, everything works well.

I tried to set button 1 to true and button 2 to false in my activity before calling the function, but it didn't work either.

Thanks for any help!


Solution

  • I think using OnCheckedChangeListener to toggle the other button is going to be a difficult way to solve the problem, because when one of the buttons is toggled, it will want to toggle the other, thereby making it want to toggle the other, and so on back and forth.

    Since you are changing the color to match a toggle state, it would be more appropriate and robust to handle that with styling. Create a StateListDrawable like this:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
      <item android:drawable="@color/yellow" android:state_checked="true"/>
      <item android:drawable="@color/white"/>
    </selector>
    

    and set it as the background of both buttons in your layout file rather than the explicit colors.

    You'll also want to set one of the buttons to have an initial state of checked:

    android:checked="true"
    

    The simple way to have them change each other is to give them each a click listener that toggles the other button.

    btn1.setOnClickListener { _ -> btn2.isChecked = !btn2.isChecked }
    btn2.setOnClickListener { _ -> btn1.isChecked = !btn1.isChecked }
    

    But in my opinion, it's not good practice to rely on UI elements to store your state if you need these to stay synced. There are UI behaviors that can be finicky (like how quickly double-tapping a Button that starts an Activity can start two copies of that Activity). So I would keep your state stored in a property. You can make it observable so changing it explicitly sets the toggle state of both buttons every time.

    private val buttonState by Delegates.observable(true) { _, _, newState ->
        btn1.isChecked = newState
        btn2.isChecked = !newState
    }
    
    //...
    
    val listener = View.OnClickListener { _ -> buttonState = !buttonState }
    btn1.onClickListener = listener
    btn2.onClickListener = listener