Search code examples
androidkotlinandroid-databindingandroid-radiogroupandroid-include

RadioGroup Android Databinding XML With Include Layouts


I am currently facing an issue with the radio buttons in my app. My app comprises Information Forms which include Radio buttons with other Views and there are multiple forms. When I select option 1 of the first and second radio buttons and select option 2 of the last radio button in my fragment and navigate to another fragment and come back to my previous fragment all the radio buttons have option 2 selected. No matter what I choose as an option for radio buttons, whatever the last radio option is selected all the radio buttons have that option selected. This happens when I navigate to another fragment and come back to the previous fragment. I want to show the correct options selected when I navigate back to the previous fragment.

Note:

I am using Hilt, DataBinding, ViewBinding, MVVM, HiltNavGraph

layout_textview_radiogroup.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="tvText"
            type="String" />

        <variable
            name="option1Text"
            type="String" />

        <variable
            name="option2Text"
            type="String" />

        <variable
            name="selectedOption"
            type="Integer" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/layout_textview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{tvText}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Some Text"/>

        <RadioGroup
            android:id="@+id/radioGroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="7dp"
            app:layout_constraintTop_toBottomOf="@id/layout_textview">

            <RadioButton
                android:id="@+id/rb_option1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:checked="@{selectedOption == @id/rb_option1 ? true : false}"
                android:text="@{option1Text}"
                tools:text="Option 1" />

            <RadioButton
                android:id="@+id/rb_option2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:checked="@{selectedOption == @id/rb_option2 ? true : false}"
                android:text="@{option2Text}"
                tools:text="Option 2" />

        </RadioGroup>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

fragment_some.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="fragment"
            type="com.yousuf.demo.radio.SomeFragment" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".radio.SomeFragment"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="20dp">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Go Back"
            android:onClick="@{() -> fragment.goBack()}" />

    </LinearLayout>
</layout>

fragment_radio.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.yousuf.demo.radio.RadioViewModel" />
        <variable
            name="fragment"
            type="com.yousuf.demo.radio.RadioFragment" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="20dp">

        <include
            android:id="@+id/layout_question1"
            layout="@layout/layout_textview_radiogroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:option1Text="@{`Option 1`}"
            app:option2Text="@{`Option 2`}"
            app:selectedOption="@={viewModel.selectedQuestion1OptionId}"
            app:tvText="@{`Question 1`}" />

        <include
            android:id="@+id/layout_question2"
            layout="@layout/layout_textview_radiogroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toBottomOf="@id/layout_question1"
            app:option1Text="@{`Option 1`}"
            app:option2Text="@{`Option 2`}"
            app:selectedOption="@={viewModel.selectedQuestion2OptionId}"
            app:tvText="@{`Question 2`}" />

        <include
            android:id="@+id/layout_question3"
            layout="@layout/layout_textview_radiogroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toBottomOf="@id/layout_question2"
            app:option1Text="@{`Option 1`}"
            app:option2Text="@{`Option 2`}"
            app:selectedOption="@={viewModel.selectedQuestion3OptionId}"
            app:tvText="@{`Question 3`}" />

        <Button
            android:id="@+id/btn_next"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> fragment.navigateToSomeFragment()}"
            android:text="Next"
            app:layout_constraintTop_toBottomOf="@id/layout_question3"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/radio_nav_graph">
    <navigation android:id="@+id/radio_nav_graph"
        app:startDestination="@id/radioFragment">
        <fragment
            android:id="@+id/radioFragment"
            android:name="com.yousuf.demo.radio.RadioFragment"
            android:label="RadioFragment">
            <action
                android:id="@+id/action_radioFragment_to_someFragment"
                app:destination="@id/someFragment" />
        </fragment>
        <fragment
            android:id="@+id/someFragment"
            android:name="com.yousuf.demo.radio.SomeFragment"
            android:label="SomeFragment" />
    </navigation>
</navigation>

BaseFragment.kt

import android.os.Bundle
import android.view.*
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuProvider
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.appbar.MaterialToolbar

abstract class BaseFragment<Type : ViewDataBinding> : Fragment() {

    private var _binding: Type? = null
    protected val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        _binding = inflateLayout(inflater)
        init()
        return binding.root
    }

    abstract fun inflateLayout(inflater: LayoutInflater): Type

    private fun init() {
        binding.lifecycleOwner = viewLifecycleOwner
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

    fun setUpToolbar(
        toolbar: MaterialToolbar,
        @DrawableRes toolbarHomeIcon: Int,
        menuProviderCallback: MenuProvider,
        viewLifecycleOwner: LifecycleOwner,
    ) {
        toolbar.setupWithNavController(
            findNavController(),
            AppBarConfiguration(findNavController().graph)
        )
        (requireActivity() as AppCompatActivity).apply {
            setSupportActionBar(toolbar)
            supportActionBar?.setDisplayHomeAsUpEnabled(true)
            supportActionBar?.setHomeAsUpIndicator(toolbarHomeIcon)
        }
        requireActivity().addMenuProvider(menuProviderCallback, viewLifecycleOwner)
    }
}

SomeFragment.kt

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.yousuf.demo.BaseFragment
import com.yousuf.demo.R
import com.yousuf.demo.databinding.FragmentSomeBinding

class SomeFragment : BaseFragment<FragmentSomeBinding>() {

    override fun inflateLayout(inflater: LayoutInflater): FragmentSomeBinding {
        return FragmentSomeBinding.inflate(inflater)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.fragment = this@SomeFragment
    }

    fun goBack() {
        findNavController().navigateUp()
    }

}

RadioFragment.kt

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
import androidx.navigation.fragment.findNavController
import com.yousuf.demo.BaseFragment
import com.yousuf.demo.R
import com.yousuf.demo.databinding.FragmentRadioBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class RadioFragment : BaseFragment<FragmentRadioBinding>() {

    private val viewModel: RadioViewModel by hiltNavGraphViewModels(R.id.radio_nav_graph)

    override fun inflateLayout(inflater: LayoutInflater): FragmentRadioBinding {
        return FragmentRadioBinding.inflate(inflater)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.viewModel = viewModel
        binding.fragment = this@RadioFragment

        setUpRadioClickListener()
    }

    private fun setUpRadioClickListener() {
        with(viewModel) {
            binding.layoutQuestion1.radioGroup.setOnCheckedChangeListener { radioGroup, _ ->
                selectedQuestion1OptionId.value = radioGroup.checkedRadioButtonId
            }
            binding.layoutQuestion2.radioGroup.setOnCheckedChangeListener { radioGroup, _ ->
                selectedQuestion2OptionId.value = radioGroup.checkedRadioButtonId
            }
            binding.layoutQuestion3.radioGroup.setOnCheckedChangeListener { radioGroup, _ ->
                selectedQuestion3OptionId.value = radioGroup.checkedRadioButtonId
            }
        }

    }

    fun navigateToSomeFragment() {
        findNavController().navigate(R.id.action_radioFragment_to_someFragment)
    }
}

RadioViewModel.kt

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class RadioViewModel @Inject constructor(): ViewModel() {

    var selectedQuestion1OptionId = MutableLiveData(-1)

    var selectedQuestion2OptionId = MutableLiveData(-1)

    var selectedQuestion3OptionId = MutableLiveData(-1)
}

Solution

  • I found the answer by playing with it a bit

    1. Added a listener for my RadioGroup

    variable name="onCheckedChangedListener" type="android.widget.RadioGroup.OnCheckedChangeListener" />

    1. Modified my RadioGroup by adding these two lines

    android:onCheckedChanged="@{onCheckedChangedListener}" android:checkedButton="@{selectedOption}"

    1. Removed one line from each RadioButton

    android:checked="@{selectedOption == @id/rb_option1 ? true : false}"

    1. Added functions in my viewModel for each RadioGroup selection
    fun setQuestion1OptionId(id: Int) {
        selectedQuestion1OptionId.value = id
    }
    
    fun setQuestion2OptionId(id: Int) {
        selectedQuestion2OptionId.value = id
    }
    
    fun setQuestion3OptionId(id: Int) {
        selectedQuestion3OptionId.value = id
    }
    
    1. Added listener to each included RadioGroup in fragment_radio.xml

    app:onCheckedChangedListener="@{(radioGroup, id) -> viewModel.setQuestion1OptionId(id)}"

    1. Removed implementation from RadioFragment

    Here is my modified Code

    layout_textview_radiogroup.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
            <variable
                name="tvText"
                type="String" />
    
            <variable
                name="option1Text"
                type="String" />
    
            <variable
                name="option2Text"
                type="String" />
    
            <variable
                name="selectedOption"
                type="Integer" />
    
            <!--Modification-->
            <variable
                name="onCheckedChangedListener"
                type="android.widget.RadioGroup.OnCheckedChangeListener" />
    
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView
                android:id="@+id/layout_textview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{tvText}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="Some Text"/>
    
            <!--Modification-->
            <RadioGroup
                android:id="@+id/radioGroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="7dp"
                android:onCheckedChanged="@{onCheckedChangedListener}"
                android:checkedButton="@{selectedOption}"
                app:layout_constraintTop_toBottomOf="@id/layout_textview">
    
                <!--Modification-->
                <RadioButton
                    android:id="@+id/rb_option1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="10dp"
                    android:text="@{option1Text}"
                    tools:text="Option 1" />
    
                <!--Modification-->
                <RadioButton
                    android:id="@+id/rb_option2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@{option2Text}"
                    tools:text="Option 2" />
    
            </RadioGroup>
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    fragment_radio.kt

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
            <variable
                name="viewModel"
                type="com.yousuf.demo.radio.RadioViewModel" />
    
            <variable
                name="fragment"
                type="com.yousuf.demo.radio.RadioFragment" />
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="20dp">
    
            <!--Modification-->
            <include
                android:id="@+id/layout_question1"
                layout="@layout/layout_textview_radiogroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintTop_toTopOf="parent"
                app:onCheckedChangedListener="@{(radioGroup, id) -> viewModel.setQuestion1OptionId(id)}"
                app:option1Text="@{`Option 1`}"
                app:option2Text="@{`Option 2`}"
                app:selectedOption="@{viewModel.selectedQuestion1OptionId}"
                app:tvText="@{`Question 1`}" />
    
            <!--Modification-->
            <include
                android:id="@+id/layout_question2"
                layout="@layout/layout_textview_radiogroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                app:layout_constraintTop_toBottomOf="@id/layout_question1"
                app:onCheckedChangedListener="@{(radioGroup, id) -> viewModel.setQuestion2OptionId(id)}"
                app:option1Text="@{`Option 1`}"
                app:option2Text="@{`Option 2`}"
                app:selectedOption="@{viewModel.selectedQuestion2OptionId}"
                app:tvText="@{`Question 2`}" />
    
            <!--Modification-->
            <include
                android:id="@+id/layout_question3"
                layout="@layout/layout_textview_radiogroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                app:layout_constraintTop_toBottomOf="@id/layout_question2"
                app:onCheckedChangedListener="@{(radioGroup, id) -> viewModel.setQuestion3OptionId(id)}"
                app:option1Text="@{`Option 1`}"
                app:option2Text="@{`Option 2`}"
                app:selectedOption="@{viewModel.selectedQuestion3OptionId}"
                app:tvText="@{`Question 3`}" />
    
            <Button
                android:id="@+id/btn_next"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{() -> fragment.navigateToSomeFragment()}"
                android:text="Next"
                app:layout_constraintTop_toBottomOf="@id/layout_question3" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    RadioFragment.kt

    import android.os.Bundle
    import android.view.LayoutInflater
    import android.view.View
    import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
    import androidx.navigation.fragment.findNavController
    import com.yousuf.demo.BaseFragment
    import com.yousuf.demo.R
    import com.yousuf.demo.databinding.FragmentRadioBinding
    import dagger.hilt.android.AndroidEntryPoint
    
    @AndroidEntryPoint
    class RadioFragment : BaseFragment<FragmentRadioBinding>() {
    
        private val viewModel: RadioViewModel by hiltNavGraphViewModels(R.id.radio_nav_graph)
    
        override fun inflateLayout(inflater: LayoutInflater): FragmentRadioBinding {
            return FragmentRadioBinding.inflate(inflater)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding.viewModel = viewModel
            binding.fragment = this@RadioFragment
        }
    
        fun navigateToSomeFragment() {
            findNavController().navigate(R.id.action_radioFragment_to_someFragment)
        }
    }
    

    RadioViewModel.kt

    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    import dagger.hilt.android.lifecycle.HiltViewModel
    import javax.inject.Inject
    
    @HiltViewModel
    class RadioViewModel @Inject constructor(): ViewModel() {
    
    
        var selectedQuestion1OptionId = MutableLiveData(-1)
    
        var selectedQuestion2OptionId = MutableLiveData(-1)
    
        var selectedQuestion3OptionId = MutableLiveData(-1)
    
        // Modification
        fun setQuestion1OptionId(id: Int) {
            selectedQuestion1OptionId.value = id
        }
    
        fun setQuestion2OptionId(id: Int) {
            selectedQuestion2OptionId.value = id
        }
    
        fun setQuestion3OptionId(id: Int) {
            selectedQuestion3OptionId.value = id
        }
    }