How to select multiple checkboxes using one checkbox in android kotlin?

3.1k Views Asked by At

Here is my activity code:-

class SelectCoursesActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_select_courses)

        all_courses.setOnCheckedChangeListener { _, _ ->
            if (all_courses.isChecked) {
                scienceCourses()
                artsCourses()
                commerceCourses()
                all_science.isChecked = true
                all_arts.isChecked = true
                all_commerce.isChecked = true
            } else {
                scienceCourses()
                artsCourses()
                commerceCourses()
                all_science.isChecked = false
                all_arts.isChecked = false
                all_commerce.isChecked = false
            }
        }


        all_science.setOnCheckedChangeListener { _, _ ->
            all_courses.isChecked =
                all_science.isChecked && all_arts.isChecked && all_commerce.isChecked
        }

        all_arts.setOnCheckedChangeListener { _, _ ->
            all_courses.isChecked =
                all_science.isChecked && all_arts.isChecked && all_commerce.isChecked
        }

        all_commerce.setOnCheckedChangeListener { _, _ ->
            all_courses.isChecked =
                all_science.isChecked && all_arts.isChecked && all_commerce.isChecked
        }
    }

    private fun commerceCourses() {
        all_commerce.setOnCheckedChangeListener { _, _ ->
            if (all_commerce.isChecked) {
                accountancy.isChecked = true
                businessStudies.isChecked = true
                physicalEducation.isChecked = true
            } else {
                accountancy.isChecked = false
                businessStudies.isChecked = false
                physicalEducation.isChecked = false
            }
        }

        accountancy.setOnCheckedChangeListener { _, _ ->
            all_commerce.isChecked =
                accountancy.isChecked && businessStudies.isChecked && physicalEducation.isChecked
        }

        businessStudies.setOnCheckedChangeListener { _, _ ->
            all_commerce.isChecked =
                accountancy.isChecked && businessStudies.isChecked && physicalEducation.isChecked
        }

        physicalEducation.setOnCheckedChangeListener { _, _ ->
            all_commerce.isChecked =
                accountancy.isChecked && businessStudies.isChecked && physicalEducation.isChecked
        }
    }

    private fun artsCourses() {
        all_arts.setOnCheckedChangeListener { _, _ ->
            if (all_arts.isChecked) {
                economics.isChecked = true
                history.isChecked = true
                politicalScience.isChecked = true
            } else {
                economics.isChecked = false
                history.isChecked = false
                politicalScience.isChecked = false
            }
        }

        economics.setOnCheckedChangeListener { _, _ ->
            all_arts.isChecked =
                economics.isChecked && history.isChecked && politicalScience.isChecked
        }

        history.setOnCheckedChangeListener { _, _ ->
            all_arts.isChecked =
                economics.isChecked && history.isChecked && politicalScience.isChecked
        }

        politicalScience.setOnCheckedChangeListener { _, _ ->
            all_arts.isChecked =
                economics.isChecked && history.isChecked && politicalScience.isChecked
        }
    }

    private fun scienceCourses() {
        all_science.setOnCheckedChangeListener { _, _ ->
            if (all_science.isChecked) {
                physics.isChecked = true
                chemistry.isChecked = true
                math.isChecked = true
            } else {
                physics.isChecked = false
                chemistry.isChecked = false
                math.isChecked = false
            }
        }

        physics.setOnCheckedChangeListener { _, _ ->
            all_science.isChecked = physics.isChecked && chemistry.isChecked && math.isChecked
        }

        chemistry.setOnCheckedChangeListener { _, _ ->
            all_science.isChecked = physics.isChecked && chemistry.isChecked && math.isChecked
        }

        math.setOnCheckedChangeListener { _, _ ->
            all_science.isChecked = physics.isChecked && chemistry.isChecked && math.isChecked
        }
    }
}

here is activity xml code:-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="20dp"
    tools:context=".SelectCoursesActivity">

    <CheckBox
        android:id="@+id/all_courses"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/all_courses"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/science"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <CheckBox
        android:id="@+id/all_science"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/all"/>

    <CheckBox
        android:id="@+id/physics"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/physics"/>

    <CheckBox
        android:id="@+id/math"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/math"/>

    <CheckBox
        android:id="@+id/chemistry"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/chemistry"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/arts"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <CheckBox
        android:id="@+id/all_arts"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/all"/>

    <CheckBox
        android:id="@+id/economics"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/economics"/>

    <CheckBox
        android:id="@+id/history"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/history"/>

    <CheckBox
        android:id="@+id/politicalScience"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/politicalScience"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/science"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <CheckBox
        android:id="@+id/all_commerce"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/all"/>

    <CheckBox
        android:id="@+id/accountancy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/accountancy"/>

    <CheckBox
        android:id="@+id/businessStudies"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/businessStudies"/>

    <CheckBox
        android:id="@+id/physicalEducation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/physicalEducation"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:backgroundTint="@null"
        android:background="@drawable/circular_button"
        android:textColor="@color/red_primary"
        android:text="@string/save"/>

</LinearLayout>

I am trying to make to activity that if:- 1)all courses checkbox is selected then all other checkboxes should be checked and vice versa.

2)all science button is checked then all other subjects buttons should be selected and vice versa. Same for all other subjects as well.

3)if any course button is not checked then all courses button should also not be checked. Same for all subjects as well.

Now I am able to check and uncheck using all courses button. But when I am unchecking any course button I am it is not unchecking all courses button.

When any course button is checked and when I uncheck any subject it unchecks all the subjects in that course. I want to uncheck only that course checkbox and that subjet all other courses should remain checked.

2

There are 2 best solutions below

0
Prasad v Bhagat On BEST ANSWER

Instead of direct checkboxes use recyclerView in this way:-

activity code:-

lateinit var recyclerView: RecyclerView
lateinit var adapter: CheckboxAdapter
lateinit var list3: List<RowModel>
lateinit var list1: List<RowModel>
lateinit var list2: List<RowModel>
lateinit var list4: List<RowModel>
lateinit var list: List<RowModel>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_select_courses)

    recyclerView = findViewById(R.id.trail_rv)

    list1 = listOf(
        RowModel(RowType.TopHeader, "", "", "", false)
    )

    list2 = listOf(
        RowModel(RowType.Course, "", "", "Science", false),
        RowModel(RowType.SubjectRow, "Physics", "", "Science", false),
        RowModel(RowType.Dummy, "Physics", "Physics", "Science", false),
        RowModel(RowType.SubjectRow, "Math", "", "Science", false),
        RowModel(RowType.Dummy, "Math", "Math", "Science", false),
        RowModel(RowType.SubjectRow, "Chemistry", "", "Science", false),
        RowModel(RowType.Dummy, "Chemistry", "Chemistry", "Science", false)
    )

    list3 = listOf(
        RowModel(RowType.Course, "", "", "Arts", false),
        RowModel(RowType.SubjectRow, "Economics", "", "Arts", false),
        RowModel(RowType.Dummy, "Economics", "Economics", "Arts", false),
        RowModel(RowType.SubjectRow, "History", "", "Arts", false),
        RowModel(RowType.Dummy, "History", "History", "Arts", false),
        RowModel(RowType.SubjectRow, "Political Science", "", "Arts", false),
        RowModel(RowType.Dummy, "Political Science", "Political Science", "Arts", false)
    )

    list4 = listOf(
        RowModel(RowType.Course, "", "", "Commerce", false),
        RowModel(RowType.SubjectRow, "Accountancy", "", "Commerce", false),
        RowModel(RowType.Dummy, "Accountancy", "Accountancy", "Commerce", false),
        RowModel(RowType.SubjectRow, "Business Studies", "", "Commerce", false),
        RowModel(RowType.Dummy, "Business Studies", "Business Studies", "Commerce", false),
        RowModel(RowType.SubjectRow, "Physical Education", "", "Commerce", false),
        RowModel(RowType.Dummy, "Physical Education", "Physical Education", "Commerce", false)
    )

    list = list1 + list2 + list4 + list3

    adapter = CheckboxAdapter(this, list)
    adapter.setList(list)
    recyclerView.adapter = adapter
    recyclerView.layoutManager = LinearLayoutManager(this)

    findViewById<Button>(R.id.showTextBtn).setOnClickListener {
        val checkboxesValue: String = selectedCheckboxes.joinToString(separator = ";\n")
        findViewById<TextView>(R.id.ShowTextView).text = checkboxesValue
    }

    findViewById<Button>(R.id.clearTextBtn).setOnClickListener {
        selectedCheckboxes.clear()
        adapter.setList(list)
    }

    }
}

adapter code:-

class CheckboxAdapter(
    private val context: Context,
    var productList: List<RowModel>,
) : RecyclerView.Adapter<CheckboxAdapter.TableViewHolder>() {


    override fun onBindViewHolder(holder: TableViewHolder, position: Int) {

        val item = productList[position]

        holder.checkBox.setOnCheckedChangeListener(null)
        holder.checkBox.isChecked = item.isChecked

        val params: ViewGroup.MarginLayoutParams =
            holder.checkBox.layoutParams as ViewGroup.MarginLayoutParams

        when (item.rowType) {
            RowType.TopHeader -> {
                holder.checkBox.text = "All Courses"
                holder.checkBox.visibility = View.VISIBLE
                holder.checkBox.typeface = Typeface.DEFAULT_BOLD
                params.setMargins(0, 0, 0, 0)
                holder.checkBox.layoutParams = params

            }
            RowType.Course -> {

                holder.checkBox.visibility = View.VISIBLE
                holder.checkBox.text = item.course
                holder.checkBox.typeface = Typeface.DEFAULT_BOLD
                params.setMargins(20, 0, 0, 0)
                holder.checkBox.layoutParams = params

            }
            RowType.SubjectRow -> {

                holder.checkBox.visibility = View.VISIBLE
                holder.checkBox.text = item.subjectName
                holder.checkBox.typeface = Typeface.DEFAULT
                params.setMargins(convertDpToPixel(40f, context).toInt(), 0, 0, 0)
                holder.checkBox.layoutParams = params
            }
            RowType.Dummy -> {

                holder.checkBox.visibility = View.GONE
                holder.checkBox.text = item.subjectName
                holder.checkBox.typeface = Typeface.DEFAULT
                params.setMargins(convertDpToPixel(60f, context).toInt(), 0, 0, 0)
                holder.checkBox.layoutParams = params
            }
        }

        holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
            if (item.isChecked != isChecked) {
                item.isChecked = isChecked

                when (item.rowType) {
                    RowType.TopHeader -> {
                        val indexList = mutableListOf<Int>()
                        productList.filter { it.rowType != RowType.TopHeader }.forEach {
                            it.isChecked = isChecked
                            indexList.add(productList.indexOf(it))
                        }
                        indexList.forEach {
                            notifyItemChanged(it)
                        }
                    }
                    RowType.Course -> {
                        val indexList = mutableListOf<Int>()
                        productList.filter { it.rowType == RowType.SubjectRow && it.course == item.course }
                            .forEach {
                                it.isChecked = isChecked
                                indexList.add(productList.indexOf(it))
                            }
                        productList.filter { it.rowType == RowType.Dummy && it.course == item.course }
                            .forEach {
                                it.isChecked = isChecked
                                indexList.add(productList.indexOf(it))
                            }
                        indexList.forEach {
                            notifyItemChanged(it)
                        }
                        isAllItemsSameStatus() //for header
                    }
                    RowType.SubjectRow -> {
                        val indexList = mutableListOf<Int>()
                        productList.filter { it.rowType == RowType.Dummy && it.subjectName == item.subjectName }
                            .forEach {
                                it.isChecked = isChecked
                                indexList.add(productList.indexOf(it))
                            }
                        indexList.forEach {
                            notifyItemChanged(it)
                        }
                        isAllItemsSameStatus()
                    }
                }
            }
        }


        when (item.rowType) {
            RowType.Course -> {
                if (holder.checkBox.isChecked)
                    selectedCheckboxes.add(holder.checkBox.text.toString())
                if (!holder.checkBox.isChecked) {
                    selectedCheckboxes.remove(holder.checkBox.text.toString())
                }
            }
            RowType.Dummy -> {
                if (holder.checkBox.isChecked)
                    selectedCheckboxes.add(holder.checkBox.text.toString())
                if (!holder.checkBox.isChecked) {
                    selectedCheckboxes.remove(holder.checkBox.text.toString())
                }
            }
        }
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TableViewHolder {
        return TableViewHolder(
            LayoutInflater.from(context).inflate(
                R.layout.checkbox,
                parent,
                false
            )
        )
    }

    class TableViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        val checkBox: CheckBox = itemView.findViewById(R.id.Checkbox)

    }

    override fun getItemCount() = productList.size


    fun setList(profiles: List<RowModel>) {
        productList = profiles
        notifyDataSetChanged()
    }

    private fun isAllItemsSameStatus(cat: String? = null) {

        val row: RowModel
        var isChecked: Boolean = true
        var position: Int = 0

        if (cat != null) {
            val catRow = productList.find { it.rowType == RowType.Course && it.course == cat }
            catRow?.let {
                val subList =
                    productList.filter { it.course == it.course && it.rowType == RowType.SubjectRow }
                isChecked = subList.filter { it.isChecked }.size == subList.size
                position = productList.indexOf(catRow)
            }
            if (catRow == null)
                return
            else
                row = catRow
        } else {
            row = productList[0]
            isChecked =
                productList.filter { it.rowType != RowType.TopHeader && it.isChecked }.size == productList.size - 1
            position = 0
        }

        updateHeader(row, isChecked, position)
    }

    private fun isAllSubjectItemsSameStatus(cat: String? = null) {

        val row: RowModel
        var isChecked: Boolean = true
        var position: Int = 0

        if (cat != null) {
            val catRow =
                productList.find { it.rowType == RowType.SubjectRow && it.subjectName == cat }
            catRow?.let {
                val subList =
                    productList.filter { it.subjectName == it.subjectName && it.rowType == RowType.Dummy }
                isChecked = subList.filter { it.isChecked }.size == subList.size
                position = productList.indexOf(catRow)
            }
            if (catRow == null)
                return
            else
                row = catRow
        } else {
            row = productList[0]
            isChecked =
                productList.filter { it.rowType != RowType.TopHeader && it.isChecked }.size == productList.size - 1
            position = 0
        }

        updateHeader(row, isChecked, position)
    }


    private fun updateHeader(item: RowModel, isChecked: Boolean, position: Int) {
        if (item.isChecked != isChecked) // no need to update if no change
        {
            item.isChecked = isChecked
            notifyItemChanged(position)

        }
    }

    private fun convertDpToPixel(dp: Float, context: Context): Float {
        return dp * (context.resources
            .displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
    }

    companion object {
        var selectedCheckboxes: ArrayList<String> = ArrayList()
    }

}

Model data class code:-

data class SubjectRowModel (
    val SubjectRowType: SubjectRowType,
    val subjectName: String,
    val dummyName: String,
    val course: String,
    var isChecked: Boolean = true)

enum class SubjectRowType(val id : Int) {

    TopHeader(1),
    Course(2),
    SubjectRow(3),
    Dummy(4);

}
7
Tenfour04 On

Create a Boolean property that can be toggled to disable all the check box listeners. Turn it off when you're going to make a change, and then turn it back on. At the beginning of every check box listener, return immediately if the property is true.

To automate this, you can create a function that does all the boilerplate of checking the lock state and doing the temporary locking while it's doing its action. Then you only have to use this function instead of setOnCheckedChangeListener for each of the CheckBoxes that should be locked while one of these actions is occurring. Below I called it setLockableListener.

Also, many of your if/else statements have duplicate code. You can collapse the logic to make your code clearer and more concise.

Finally, I moved scienceCourses(), artsCourses(), commerceCourses() outside your first listener. I don't see any reason they should be repeatedly called each time the check box is clicked.

class SelectCoursesActivity : AppCompatActivity() {

    private var areCheckBoxListenersLocked = false

    private inline fun CheckBox.setLockableListener(
        crossinline actionWhenUnlocked: (isChecked: Boolean) -> Unit
    ) {
        setOnCheckedChangeListener { _, isChecked ->
            if (!areCheckBoxListenersLocked) {
                areCheckBoxListenersLocked = true
                actionWhenUnlocked(isChecked)
                areCheckBoxListenersLocked = false
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_select_courses)

        scienceCourses()
        artsCourses()
        commerceCourses()

        all_courses.setLockableListener { isChecked ->
            all_science.isChecked = isChecked
            all_arts.isChecked = isChecked
            all_commerce.isChecked = isChecked
        }

        listOf(all_science, all_arts, all_commerce).forEach { 
            it.setLockableListener { 
                all_courses.isChecked =
                    all_science.isChecked && all_arts.isChecked && all_commerce.isChecked
            }
        }
    }

    private fun commerceCourses() {
        all_commerce.setLockableListener { isChecked ->
            accountancy.isChecked = isChecked 
            businessStudies.isChecked = isChecked 
            physicalEducation.isChecked = isChecked 
        }

        listOf(accountancy, businessStudies, physicalEducation).forEach { 
            it.setLockableListener { 
                all_commerce.isChecked =
                    accountancy.isChecked && businessStudies.isChecked && physicalEducation.isChecked
            }
        }

    }

    // ...and so on...

}

Edit: I didn't check your logic in detail, but here's a way to do it through iteration, which tends to be a more manageable and less error-prone way to write code. Create a map of each check box to its "parent". Use this to also create an inverse map of check boxes to their children. Every checkbox can share the same listener. The listener updates any children to match the one that just changed, and then calls a function that recursively updates its parents to match the latest state. By starting at the bottom parent and working up, each parent only has to check the state of its direct children to know if it should be checked.

class SelectCoursesActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_select_courses)

        val parentByChild = mapOf(
           all_science to all_courses,
           all_arts to all_courses,
           all_commerce to all_courses,
           physics to all_science,
           chemistry to all_science,
           math to all_science,
           economics to all_arts,
           history to all_arts,
           politicalScience to all_arts,
           accountancy to all_commerce, 
           businessStudies to all_commerce, 
           physicalEducation to all_commerce
        )

        val childrenByParent = parentByChild.entries
            .groupBy(Map.Entry<CheckBox, CheckBox>::value, Map.Entry<CheckBox, CheckBox>::key)

        var listenerLocked = false
        val listener = OnCheckedChangedListener { checkBox, isChecked ->
            if (listenerLocked) {
                return@OnCheckedChangedListener 
            }
            listenerLocked = true
            // Changing a parent means you want all children, if any, changed to match it.
            childrenByParent[checkBox]?.forEach { it.isChecked = isChecked }

            // Recursively tell parents up the chain to appear checked if
            // and only if all their direct children are checked.
            fun CompoundButton.update() {
                isChecked = childrenByParent[parent]!!.all { it.isChecked }
                parentByChild[this]?.update()
            }
            parentByChild[checkBox]?.update()
            listenerLocked = false
        }

        for (checkBox in parentByChild.keys) {
            checkBox.onCheckedChangedListener = listener
        }
    }
}