Home > Software engineering >  Android Kotlin Observer not detecting LiveData Change
Android Kotlin Observer not detecting LiveData Change

Time:01-26

I have a simple Android Kotlin app that i am working on which involves multiple fragments communicating with one viewModel. The problem is the LiveData observer in my first fragment will not update every time the list changes in my view model. Can anyone explain where i might be going wrong?

Here is my fragment:

class ShoeDetailsFragment : Fragment() {

lateinit var binding: FragmentShoeDetailsBinding
private val viewModel: ShoeViewModel by activityViewModels()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
    // Inflate the layout for this fragment
    binding = DataBindingUtil.inflate(inflater, R.layout.fragment_shoe_details, container, false)

    setClickListeners()
    watchForChanges()
    return binding.root
}

private fun watchForChanges(){
    viewModel.shoeList.observe(viewLifecycleOwner) { list ->
        Log.i("Contents of list: ", list.toString())
        binding.viewModelTestEditText.setText(list.toString())
    }
}

private fun createShoeFromInputs(): Shoe {
    val shoeNameString = binding.shoeNameEditText.text.toString()
    val shoeColourString = binding.shoeColourEditText.text.toString()
    val shoeMakerString = binding.shoeMakerEditText.text.toString()
    val shoeSizeString = binding.shoeSizeEditText.text.toString()

    return Shoe(shoeNameString, shoeColourString, shoeMakerString, shoeSizeString)
}



private fun setClickListeners(){
    binding.saveButtonShoeDetails.setOnClickListener{
        saveShoeToList(createShoeFromInputs())
    }

    binding.cancelButtonShoeDetails.setOnClickListener{
        viewModel.removeShoeFromShoeList()
    }
}

private fun saveShoeToList(shoe: Shoe){
    if (validateFields()){
        viewModel.addShoeToShoeList(shoe)
    }
}

private fun validateFields(): Boolean{
    return if (binding.shoeNameEditText.text.isEmpty()
        || binding.shoeColourEditText.text.isEmpty()
        || binding.shoeMakerEditText.text.isEmpty()
        || binding.shoeSizeEditText.text.isEmpty()){
        Toast.makeText(requireContext(), "Please complete all fields", Toast.LENGTH_SHORT).show()
        false
    } else {
        true
    }
}
}

And here is my viewModel:

class ShoeViewModel: ViewModel() {

private val _shoeList = MutableLiveData<MutableList<Shoe>>()
val shoeList: LiveData<MutableList<Shoe>> get () =_shoeList

init {
    _shoeList.value = mutableListOf()
}

fun addShoeToShoeList(shoe: Shoe){
    _shoeList.value!!.add(shoe)
    Log.i("Contents of list in view model: ", _shoeList.value!!.size.toString())
}

fun removeShoeFromShoeList(){
    _shoeList.value!!.removeAt(0)
    Log.i("Contents of list in view model after cancel: ", _shoeList.value!!.size.toString())
}

}

I have checked the code over and over again but there must be something i am missing

CodePudding user response:

You need to call MuableLiveData.setValue() or MutableLiveData.postValue() for event to be emited.

try :

fun addShoeToShoeList(shoe: Shoe){
    val currentList = _shoeList.value ?: mutableListOf()
    currentList.add(shoe)
    _shoeList.value = Collections.copy(currentList)
    Log.i("Contents of list in view model: ", _shoeList.value!!.size.toString())
}

fun removeShoeFromShoeList(){
    val currentList = _shoeList.value ?: mutableListOf()
    currentList.removeAt(0)
    _shoeList.value=currentList
    Log.i("Contents of list in view model after cancel: ", _shoeList.value!!.size.toString())
}

CodePudding user response:

You haven't changed the value of the LiveData. It's still pointing at the same instance of a MutableList. You modified the contents of that MutableList, but the LiveData doesn't know anything about you doing that, so it will not notify observers.

I strongly recommend that you only use read-only Lists with LiveData. Instead of mutating the list, you create a new list and set it as the new value of the LiveData.

class ShoeViewModel: ViewModel() {

    private val _shoeList = MutableLiveData<List<Shoe>>()
    val shoeList: LiveData<List<Shoe>> get () =_shoeList
    
    init {
        _shoeList.value = emptyList()
    }
    
    fun addShoeToShoeList(shoe: Shoe){
        _shoeList.value = _shoeList.value.orEmpty()   shoe
        Log.i("Contents of list in view model: ", _shoeList.value.orEmpty().size.toString())
    }
    
    fun removeShoeFromShoeList(){
        _shoeList.value = _shoeList.value.orEmpty().drop(1)
        Log.i("Contents of list in view model after cancel: ", _shoeList.value.orEmpty().size.toString())
    }

}

Note, it is possible to use a MutableList, and then call liveData.value = liveData.value each time after you mutate the list to trigger it to notify observers. The reason I recommend you not do this is that some view classes (notably RecyclerView's ListAdapter) are "smart" and compare old and new data to determine whether they actually need to show any changes. If the old and new data are both the same instance of MutableList, it will not detect any changes so the UI will not update.

  • Related