Search code examples
androidkotlinmvvmandroid-livedataandroid-viewmodel

The app not responding when try to change MutableLiveData value inside observer


In this app "Guess it" game, I trying to detect whenever the game is finished when the user reach the end of wordList, in GameViewModel the _eventGameFinish val is is responsible for that

GameViewModel

class GameViewModel : ViewModel() {


    // The current word
    private val _word = MutableLiveData<String>()
    val word: LiveData<String>
        get() = _word


    // The current score
    private val _score = MutableLiveData<Int>()
    val score: LiveData<Int>
        get() = _score


    private val _eventGameFinish = MutableLiveData<Boolean>()
    val eventGameFinish : LiveData<Boolean>
            get()= _eventGameFinish


    // The list of words - the front of the list is the next word to guess
    private lateinit var wordList: MutableList<String>

    init {
        Log.i("GameViewModel", "GameViewModel created!")
        resetList()
        nextWord()
        _score.value = 0
        _eventGameFinish.value = false
    }

    /**
     * Resets the list of words and randomizes the order
     */
    private fun resetList() {
        wordList = mutableListOf(
            "queen",
            "hospital",
            "basketball",
            "cat",
            "change",
            "snail",
            "soup",
            "calendar",
            "sad",
            "desk",
            "guitar",
            "home",
            "railway",
            "zebra",
            "jelly",
            "car",
            "crow",
            "trade",
            "bag",
            "roll",
            "bubble"
        )
        wordList.shuffle()
    }

    /**
     * Moves to the next word in the list
     */
    private fun nextWord() {
        //Select and remove a word from the list
        if (wordList.isEmpty()) {
            _eventGameFinish.value = true
        } else {
            _word.value = wordList.removeAt(0)
        }
    }

    /** Methods for buttons presses **/

    fun onSkip() {
        _score.value = score.value?.minus(1)
        nextWord()
    }

    fun onCorrect() {
        _score.value = score.value?.plus(1)
        nextWord()
    }

    fun onGameFinishComplete(){
        _eventGameFinish.value = false
    }

    override fun onCleared() {
        super.onCleared()
        Log.i("GameViewModel", "GameViewModel destroyed")
    }
}

In GameFragment I observe on eventGameFinish then calling gameViewModel.onGameFinishComplete() to make the action just once in the fragment

here's GameFragment class

class GameFragment : Fragment() {

    private lateinit var gameViewModel: GameViewModel


    private lateinit var binding: GameFragmentBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        // Inflate view and obtain an instance of the binding class
        binding = DataBindingUtil.inflate(
            inflater,
            R.layout.game_fragment,
            container,
            false
        )

        Log.i("GameViewModelOf", "GameViewModelOf")
        gameViewModel = ViewModelProvider(this).get(GameViewModel::class.java)



        binding.correctButton.setOnClickListener {

            gameViewModel.onCorrect()

        }
        binding.skipButton.setOnClickListener {
            gameViewModel.onSkip()

        }


        gameViewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
            binding.scoreText.text = newScore.toString()
        })

        gameViewModel.word.observe(viewLifecycleOwner, Observer { newWord ->
            binding.wordText.text = newWord

        })

        gameViewModel.eventGameFinish.observe(viewLifecycleOwner, Observer { eventGameFinish ->
            if (eventGameFinish) this.gameFinished()
            gameViewModel.onGameFinishComplete()
        })

        return binding.root

    }


    /**
     * Called when the game is finished
     */
    private fun gameFinished() {
//        val action = GameFragmentDirections.actionGameToScore(gameViewModel.score.value ?: 0)
//        findNavController(this).navigate(action)
        Toast.makeText(this.activity, "Game finished", Toast.LENGTH_SHORT).show()
    }
}

the problem is in this line gameViewModel.onGameFinishComplete() when I calling the method inside Observer block when I running the app it's not responding without any exception, If I removed this call of method the app is working fine, but I can't change the value of eventGameFinish when reach the end of wordList

enter image description here


Solution

  • You are observing "eventGameFinish" ,if it is true it will call gameFinished() method and onGameFinishComplete() code set new value to eventGameFinish.so observer again will observe new value .now it is false, it will call gameViewModel() again and again and set false.that's why you got ANR.

    So call methods only if live data value is true.OR use separate MutableLiveData data to set value after game finished .

      if (eventGameFinish) 
    {
        this.gameFinished()
        gameViewModel.onGameFinishComplete()
    }