Search code examples
androidarrayskotlinandroid-livedatamutablelist

MutableList is not updated


I try to update MutableList's value within LiveData by function "addToArray". But I get null, when I try to observe liveData into a fragment. What's happening? Please, any Advice?! May be, I need to change a type within LiveData?! But, what should I choose? I use ViewModelFactory for initialize viewModel.

class MainViewModel(private val point: Int, private val index: Int) : ViewModel() {

    private var _arrayOne = MutableLiveData<MutableList<Int?>?>()
    val arrayOne: LiveData<MutableList<Int?>?> = _arrayOne

    fun addToArray(index: Int, point: Int) {
        val value = _arrayOne.value
        value?.add(point)
        _arrayOne.value = value
        Log.d("ViewModel", "$value")
    }

    private lateinit var binding: FragmentMainBinding
    private var point = 0
    private var index = 0

    private val viewModelFactory: MainViewModelFactory by lazy {
        MainViewModelFactory(point, index)
    }
    private val viewModel by lazy {
        ViewModelProvider(this, viewModelFactory)[MainViewModel::class.java]
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val array = initializeFirstParticipant()
        for (item in array) {
            item.setOnEditorActionListener { textView, i, keyEvent ->
                if (textView.text.toString().isEmpty()) {
                    textView.setBackgroundResource(R.drawable.background_outlined_red)
                    Toast.makeText(requireContext(), "Enter valid value!", Toast.LENGTH_SHORT)
                        .show()
                    true
                } else {
                    val point = Integer.parseInt(textView.text.toString())
                    val index = array.indexOf(item)
                    viewModel.addToArray(index, point)
                    Log.d("MainFragment", "Index is $index, value is $point")
                    viewModel.arrayOne.observe(viewLifecycleOwner, Observer {
                        Log.d("MainFragment", "Array is $it")
                    })
                    textView.setBackgroundResource(R.drawable.background_outlined)

                    false
                }
            }
        }
    }

    /* Return array of view for editing score for first participant */
    fun initializeFirstParticipant(): Array<TextView> {
        val tvArray = arrayOf<TextView>(
            binding.partOnePointTwo,
            binding.partOnePointThree,
            binding.partOnePointFour,
            binding.partOnePointFive,
            binding.partOnePointSix,
            binding.partOnePointSeven
        )
        return tvArray
    }
}`

I try to set Array instead List type Within LiveData, but it didn't help.     May be, I need to use another type within LiveData?!                                    

Solution

  • You never actually created a MutableList to put in your LiveData. Notice you never called mutableListOf or arrayListOf() anywhere in your code?

    There is also rarely a need to make the type of a LiveData nullable, especially with Lists, because you can use empty lists to represent there being no data. Avoid nullable stuff wherever possible, because it makes code more difficult to work with. (In Java we avoid nullability because it makes code fragile, i.e. crash-prone. Kotlin fixed the fragility by enforcing null checks, but this makes nullable variables harder to handle correctly.)

    You should also make the type of the list non-nullable if you can help it.

    So, I would first change it as follows:

    private var _arrayOne = MutableLiveData<MutableList<Int>>(mutableListOf())
    val arrayOne: LiveData<MutableList<Int>> = _arrayOne
    

    However, I also want to mention, it is generally a bad idea to use a MutableList in a LiveData for a couple of reasons:

    • Poor encapsulation. You are advertising to the observer (which is in another class) that it makes sense and is OK for it to directly modify the contents of the list.
    • Unable to detect changes. If your observer passes this list to a class that compares previous data to new data, such as ListAdapter (which does this to optimize how it refreshes itself and provides automatic animations of items moving or being added/removed), it will break because you are mutating the old data before telling it about new data, so it cannot detect any changes between old and new.

    So it is much preferable to use a read-only List instead. So you can update your code to:

    class MainViewModel(private val point: Int, private val index: Int) : ViewModel() {
    
        private var _arrayOne = MutableLiveData<List<Int>>(emptyList())
        val arrayOne: LiveData<List<Int>> = _arrayOne
    
        fun addToArray(index: Int, point: Int) {
            _arrayOne.value = _arrayOne.value.orEmpty() + point
            Log.d("ViewModel", "$value")
        }
    

    The orEmpty() above is an easy way to handle the fact that LiveData.value always returns a nullable even if the type isn't nullable. In this case, we know it's never null, but I like to avoid using !!.