So I have two NumberPicker
, the array of second picker will be based on the selections for the first picker. However in the following code, I got crush if I choose 4 han
from 5 han
by following exception
java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
at android.widget.NumberPicker.ensureCachedScrollSelectorValue(NumberPicker.java:2010)
at android.widget.NumberPicker.initializeSelectorWheelIndices(NumberPicker.java:1822)
at android.widget.NumberPicker.setMaxValue(NumberPicker.java:1520)
at com.vincent.majcal.ui.picker.PickerFragment.onCreateView$lambda$1(PickerFragment.kt:114)
Here is the code for this part, I use the Bottom Nagivation View Activity
Template and modify it with my own use:
package com.vincent.majcal.ui.picker
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.NumberPicker
import android.widget.Switch
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.vincent.majcal.databinding.FragmentPickerTestBinding
import com.vincent.majcal.databinding.FragmentSearchBinding
class PickerFragment : Fragment() {
private var _binding: FragmentPickerTestBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
val pickerViewModel = ViewModelProvider(this).get(PickerViewModel::class.java)
_binding = FragmentPickerTestBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textSearch
val firstPicker: NumberPicker = binding.firstPicker
val secondPicker: NumberPicker = binding.secondPicker
var fan = "1 han"
val selections = mapOf(
"1 han" to arrayOf(
"30",
"40",
"50",
"60",
"70",
"80",
"90",
"100",
"110"
),
"2 han" to arrayOf(
"20",
"25",
"30",
"40",
"50",
"60",
"70",
"80",
"90",
"100",
"110"
),
"3 han" to arrayOf(
"20",
"25",
"30",
"40",
"50",
"60",
"70+",
),
"4 han" to arrayOf(
"20",
"25",
"30",
"40+",
),
"5 han" to arrayOf(
"Mangan",
),
"6 ~ 7 han" to arrayOf(
"Haneman",
),
"8 ~ 10 han" to arrayOf(
"Baiman",
),
"11 ~ 12 han" to arrayOf(
"Sanbaiman",
),
"13 han+" to arrayOf(
"Kazoe Yakuman",
)
)
firstPicker.minValue = 0
firstPicker.maxValue = selections.keys.size - 1
firstPicker.displayedValues = selections.keys.toTypedArray()
firstPicker.wrapSelectorWheel = false
secondPicker.wrapSelectorWheel = false
secondPicker.minValue = 0
// default the max value to the first Picker value set
var secondDisplayArray = selections[fan]
if (secondDisplayArray != null) {
secondPicker.maxValue = secondDisplayArray.size - 1
}
secondPicker.displayedValues = secondDisplayArray
firstPicker.setOnValueChangedListener { picker, oldVal, newVal ->
run {
fan = selections.keys.elementAt(newVal)
secondDisplayArray = selections[fan]
if (secondDisplayArray != null) {
var secondDisplayArraySize: Int = secondDisplayArray!!.size
secondPicker.value = 0
secondPicker.maxValue = secondDisplayArraySize - 1 //the Logcat show this line cause the exception
secondPicker.displayedValues = secondDisplayArray
}
}
}
pickerViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Try changing the order of the operations in the
OnValueChangedListener
forfirstPicker
to ensuredisplayedValues
is updated beforemaxValue
. The reason issecondPicker.maxValue = secondDisplayArraySize - 1
occurs beforesecondPicker.displayedValues = secondDisplayArray
so ifNumberPicker
try to access values based on the oldmaxValue
(before the newdisplayedValues
array is set), that could lead to reach beyond the bounds thus causing theArrayIndexOutOfBoundsException
.I'd also clear
secondPicker.value
before updating other vars, to ensure it is within bounds.