1

Let me start off by saying my Kotlin experience is limited. I am trying to get a nullable var from a class, but get a null pointer whenever I execute it.

Class code:

class MultiSpinner : Spinner, OnMultiChoiceClickListener, OnCancelListener {

    private var items: List<String>? = null
    private var selected: BooleanArray? = null
    private var selectedNew: BooleanArray? = null
    private var defaultText: String? = null
    private var listener: MultiSpinnerListener? = null
    var category: String = "010"

    constructor(context: Context) : super(context) {}

    constructor(arg0: Context, arg1: AttributeSet) : super(arg0, arg1) {}

    constructor(arg0: Context, arg1: AttributeSet, arg2: Int) : super(arg0, arg1, arg2) {}

    override fun onClick(dialog: DialogInterface, which: Int, isChecked: Boolean) {
        selectedNew!![which] = isChecked
    }

    override fun onCancel(dialog: DialogInterface) {
        // refresh text on spinner
        val spinnerBuffer = StringBuffer()
        var allUnselected = true
        for (i in items!!.indices) {
            if (selected!![i]) {
                spinnerBuffer.append(items!![i])
                spinnerBuffer.append(", ")
                allUnselected = false
            }
            selectedNew!![i] = selected!![i]
        }

        var spinnerText: String?
        if (allUnselected) {
            spinnerText = defaultText
        } else {
            spinnerText = spinnerBuffer.toString()

            /** Remove trailing comma*/
            spinnerText = spinnerText.substring(0, spinnerText.length - 2)
        }
        val adapter = ArrayAdapter(
            context,
            R.layout.simple_spinner_item,
            arrayOf(spinnerText)
        )
        setAdapter(adapter)

        category = selected!!.joinToString(limit = selected!!.size, separator = "") {it.toInt().toString()}
        listener!!.onItemsSelected(selected)
    }

    override fun performClick(): Boolean {
        val builder = AlertDialog.Builder(context)
        builder.setMultiChoiceItems(
            items!!.toTypedArray(), selectedNew, this
        )
        builder.setPositiveButton(R.string.ok
        ) { dialog, _ -> selected = selectedNew!!.copyOf(); dialog.cancel() }
        builder.setNegativeButton(R.string.cancel
        ) { dialog, _ -> dialog.cancel() }
        builder.setOnCancelListener(this)
        builder.show()
        return true
    }

    fun setItems(
        items: List<String>, allText: String,
        listener: MultiSpinnerListener
    ) {
        this.items = items
        this.defaultText = allText
        this.listener = listener

        // one selected by default
        selected = BooleanArray(items.size) {false}
        selectedNew = BooleanArray(items.size) {false}
        for (i in selected!!.indices) {
            selected!![i] = false
            selectedNew!![i] = false
        }

        selected!![0] = true
        selectedNew!![0] = true
        category = selected!!.joinToString(limit = selected!!.size, separator = "") {it.toInt().toString()}

        // all text on the spinner
        val adapter = ArrayAdapter(
            context,
            R.layout.simple_spinner_item, arrayOf(allText)
        )
        setAdapter(adapter)
    }

    fun getSelected(): BooleanArray? {
        return selected
    }

    interface MultiSpinnerListener {
        fun onItemsSelected(selected: BooleanArray?)
    }

    private fun Boolean.toInt() = if (this) 1 else 0
}

Initializing

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    val spinnerCategory: MultiSpinner = findViewById(R.id.category_spinner)
    val categoryList: List<String> = resources.getStringArray(R.array.category).toList()

    spinnerCategory.setItems(categoryList, getString(R.string.default_category), this)
    ...
}

Accessing selected from MultiSpinner

val selected: BooleanArray? = MultiSpinner(this).getSelected()
println(selected)

The println reads null.

I have tried returning BooleanArray in stead of BooleanArray? for getSelected, but that just gave me a null pointer exception (as expected from the println reading).

I circumvented the problem for now by using category in the class and performing the manipulation inside the class. However, I would like to get the selected array in my main activity, and use its data to create my desired String outside of the class.

Why do I not get my selected data but instead a null pointer?

N Simons
  • 13
  • 2

2 Answers2

1

You have to access selected using the instance obtained from findViewById (the one already existing in your view hierarchy) instead of creating a completely new object by yourself:

val selected: BooleanArray? = spinnerCategory.getSelected()

Edit:

If you need to access selected outside the onCreate method simply store spinnerCategory as a property instead of a local variable:

private lateinit var spinnerCategory: MultiSpinner

override fun onCreate(savedInstanceState: Bundle?) {
    spinnerCategory = findViewById(R.id.category_spinner)
    ...
}

Also Kotlin has a nice feature for Android called View Binding which allows you to omit calling findViewById and reference your views using their ids directly:

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    category_spinner.setItems(categoryList, getString(R.string.default_category), this)
    ...
}

private fun doSomethingWithSelected() {
    val selected: BooleanArray? = category_spinner.getSelected()
}

However in that case I'd recommend changing the view's id so it agrees with the Kotlin variable naming guidelines, e.g. to spinnerCategory as it's already being used.

jsamol
  • 3,042
  • 2
  • 16
  • 27
0

You initialized selected with null meaning it can be nullable, so the compiler think it might possible that you will get null in this parameter. If you don't want it to be null you can define the variable like so:

private var selected: BooleanArray = false

Now selected parameter won't be nullable at any time.

Nizan Ifrach
  • 131
  • 3
  • The problem is not whether it can contain `null`, but rather why I am getting `null` – N Simons Dec 23 '19 at 12:50
  • In that case, you need to access `spinnerCategory` because the object is created inside the `onCreate` method. If you want to access it from outside of the method you should do: `var spinnerCategory: MultiSpinner override fun onCreate(savedInstanceState: Bundle?) { ... spinnerCategory: = findViewById(R.id.category_spinner) val categoryList: List = resources.getStringArray(R.array.category).toList() spinnerCategory.setItems(categoryList, getString(R.string.default_category), this) ... }` – Nizan Ifrach Dec 23 '19 at 13:51
  • thanks, this worked (with some additions). using `private lateinit var spinnerCategory: MultiSpinner` did the trick. – N Simons Dec 23 '19 at 14:10