androidkotlinandroid-recyclerviewcheckboxlistandroid-checkbox

How to get values of all the checkboxes checked using android kotlin? I am getting null pointer Exception


for logic of the question you can refer to this question:- question Logic

After lots of efforts I have came to this point. Please help me solving this problem.

Here is activity code:-

class CheckboxesActivity : AppCompatActivity() {

    lateinit var recyclerView: RecyclerView
    lateinit var adapter: CheckboxAdapter


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_trail)
        recyclerView =findViewById(R.id.trail_rv)

        val list = listOf(
            RowModel(RowType.TopHeader, "", "", false),
            RowModel(RowType.Course, "", "Science", false),
            RowModel(RowType.SubjectRow, "Physics", "Science", false),
            RowModel(RowType.SubjectRow, "Math", "Science", false),
            RowModel(RowType.SubjectRow, "Chemistry", "Science", false),
            RowModel(RowType.Course, "", "Arts", false),
            RowModel(RowType.SubjectRow, "Economics", "Arts", false),
            RowModel(RowType.SubjectRow, "History", "Arts", false),
            RowModel(RowType.SubjectRow, "Political Science", "Arts", false),
            RowModel(RowType.Course, "", "Commerce", false),
            RowModel(RowType.SubjectRow, "Accountancy", "Commerce", false),
            RowModel(RowType.SubjectRow, "Business Studies", "Commerce", false),
            RowModel(RowType.SubjectRow, "Physical Education", "Commerce", false)
        )

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

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

    }
}

here is my 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.category


                holder.checkBox.typeface = Typeface.DEFAULT_BOLD
                params.setMargins(0, 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(20f, 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.category == item.category }
                            .forEach {
                                it.isChecked = isChecked
                                indexList.add(productList.indexOf(it))
                            }
                        indexList.forEach {
                            notifyItemChanged(it)
                        }
                        isAllItemsSameStatus() //for header

                    }
                    RowType.SubjectRow -> {
                        isAllItemsSameStatus(item.category) //set prep area accordingly
                        isAllItemsSameStatus() //set top header
                    }
                }
            }
        }

        when (item.rowType) {
            RowType.SubjectRow -> {
                if (holder.checkBox.isChecked)
                    selectedCheckboxes?.add(holder.checkBox.text.toString())
                if (!holder.checkBox.isChecked) {
                    Toast.makeText(context, holder.checkBox.text, Toast.LENGTH_SHORT).show()
                    selectedCheckboxes?.remove(holder.checkBox.text.toString())
                }
            }
        }
    }


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

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

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

    }

    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.category == cat }
            catRow?.let {
                val subList =
                    productList.filter { it.category == it.category && 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 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: MutableList<String>? = null
    }

}

here is my model code:-

data class RowModel (
    val rowType: RowType,
    val subjectName: String,
    val category: String,
    var isChecked: Boolean = true)

enum class RowType(val id : Int) {

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

}

here is activity_trail layout 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"
    tools:context=".CheckboxesActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/trail_rv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/showTextBtn"
        style="@style/ButtonTransparent"
        android:text="@string/save"/>

    <TextView
        android:id="@+id/ShowTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</LinearLayout>

The logic of the question is :-

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.

After clicking showTextBtn (Button). The app is crashing. Here is the crash report:-

java.lang.NullPointerException
        at com.trail.CheckboxesActivity.onCreate$lambda-0(CheckboxesActivity.kt:45)
        at com.trail.CheckboxesActivity.$r8$lambda$Q7Wv2MEY_mNrpZmgjbfsuGABR0o(Unknown Source:0)
        at com.trail.CheckboxesActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
        at android.view.View.performClick(View.java:8160)
        at android.widget.TextView.performClick(TextView.java:16222)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1194)
        at android.view.View.performClickInternal(View.java:8137)
        at android.view.View.access$3700(View.java:888)
        at android.view.View$PerformClick.run(View.java:30236)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8653)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

Here I am trying to get the text of all the selected checkboxes. Show that text in the textView. Every other thing is working fine but the problem is only while getting the values of selected checkboxes text app is crashing. If possible can you tell me if how can I use string-array instead of typing all these list?


Solution

  • Instead of using :-

    var selectedCheckboxes: MutableList<String>? = null
    

    use:-

    var selectedCheckboxes: ArrayList<String> = ArrayList()