尝试在空对象引用上调用虚拟方法“void androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)”(Android Kotlin)

The*_*oob 3 android kotlin android-livedata

抱歉,标题很长,但是,我不确定这个错误在我的代码中的位置,但是,我确实怀疑错误在于 和 的实现liveDataObservation

我正在开发的应用程序是一个 Unscrambler 单词应用程序,用户必须解读片段上显示的字母。到目前为止,我的代码由下面列出的 2 个 Kotlin 类组成:afragment和 aViewModel类。

我目前已将变量分配_currentScrambledWord为 aMutableLiveData<String>()并利用了支持属性ViewModel.kt

private val _currentScrambledWord = MutableLiveData<String>()
val currentScrambledWord: LiveData<String>
    get() = _currentScrambledWord
Run Code Online (Sandbox Code Playgroud)

GameFragment然后我尝试使用下面的代码将观察者附加到。

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
        { newWord -> binding.textViewUnscrambledWord.text = newWord
        })
Run Code Online (Sandbox Code Playgroud)

据我了解,通过减少所需的代码量,LiveData简化了检索数据的过程。ViewModel

这是我在Logcat中发现的错误

Attempt to invoke virtual method 'void androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)' on a null object reference 
Run Code Online (Sandbox Code Playgroud)

logcat中的错误行如下

at com.example.android.unscramble.ui.game.GameViewModel.getNextWord(GameViewModel.kt:57)
at com.example.android.unscramble.ui.game.GameViewModel.<init>(GameViewModel.kt:18)
at com.example.android.unscramble.ui.game.GameFragment.getViewModel(GameFragment.kt:39)
at com.example.android.unscramble.ui.game.GameFragment.onViewCreated(GameFragment.kt:76)
Run Code Online (Sandbox Code Playgroud)

视图模型.kt

class GameViewModel:ViewModel() {

private var wordsList: MutableList<String> = mutableListOf()
private lateinit var currentWord: String

init {
    Log.d("GameFragment", "GameViewModel created!")
    getNextWord()
}

private var _score = 0
val score: Int
    get() = _score

private var _currentWordCount = 0
val currentWordCount: Int
    get() = _currentWordCount

private var _currentScrambledWord = MutableLiveData<String>()
val currentScrambledWord: LiveData<String>
    get() = _currentScrambledWord

private fun increaseScore() {
    _score += SCORE_INCREASE
}

fun isUserWordCorrect(playerWord: String): Boolean {
    if (playerWord.equals(currentWord, true)) {
        increaseScore()
        return true
    }
    return false
}


private fun getNextWord() {
    currentWord = allWordsList.random()
    val tempWord = currentWord.toCharArray()
    tempWord.shuffle()

    while (String(tempWord).equals(currentWord, false)) {
        tempWord.shuffle()
    }
    if (wordsList.contains(currentWord)) {
        getNextWord()
    } else {
        _currentScrambledWord.value = String(tempWord)
        ++_currentWordCount
        wordsList.add(currentWord)
    }
}
fun nextWord(): Boolean {
    return if (currentWordCount < MAX_NO_OF_WORDS) {
        getNextWord()
        true
    } else false
}



override fun onCleared() {
    super.onCleared()
    Log.d("GameFragment", "GameViewModel destroyed!")
}

fun reinitializeData() {
    _score = 0
    _currentWordCount = 0
    wordsList.clear()
    getNextWord()
}





}
Run Code Online (Sandbox Code Playgroud)

游戏片段.kt

class GameFragment : Fragment() {




private val viewModel: GameViewModel by viewModels()


// Binding object instance with access to the views in the game_fragment.xml layout
private lateinit var binding: GameFragmentBinding

// Create a ViewModel the first time the fragment is created.
// If the fragment is re-created, it receives the same GameViewModel instance created by the
// first fragment



override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding = GameFragmentBinding.inflate(inflater, container, false)
    Log.d("GameFragment1", "GameFragment created/re-created!")



    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    Log.d("OnViewCreated", "OnViewCreated working")
    // Setup a click listener for the Submit and Skip buttons.
    binding.submit.setOnClickListener { onSubmitWord() }
    binding.skip.setOnClickListener { onSkipWord() }
    // Update the UI

    binding.score.text = getString(R.string.score, 0)
    binding.wordCount.text = getString(
            R.string.word_count, 0, MAX_NO_OF_WORDS)

    viewModel.currentScrambledWord.observe(viewLifecycleOwner,
        { newWord -> binding.textViewUnscrambledWord.text = newWord
        })
}

/*
* Checks the user's word, and updates the score accordingly.
* Displays the next scrambled word.
*/


private fun onSubmitWord() {
    val playerWord = binding.textInputEditText.text.toString()

    if (viewModel.isUserWordCorrect(playerWord)) {
        setErrorTextField(false)
        if (!viewModel.nextWord()) {
            showFinalScoreDialog()
        }
    } else {
        setErrorTextField(true)
    }
}



/*
 * Skips the current word without changing the score.
 * Increases the word count.
 */
private fun onSkipWord() {
    if (viewModel.nextWord()) {
        setErrorTextField(false)

    } else {
        showFinalScoreDialog()
    }
}

/*
 * Gets a random word for the list of words and shuffles the letters in it.
 */
private fun getNextScrambledWord(): String {
    val tempWord = allWordsList.random().toCharArray()
    tempWord.shuffle()
    return String(tempWord)
}

/*
 * Re-initializes the data in the ViewModel and updates the views with the new data, to
 * restart the game.
 */

private fun showFinalScoreDialog() {
    MaterialAlertDialogBuilder(requireContext())
        .setTitle(getString(R.string.congratulations))
        .setMessage(getString(R.string.you_scored, viewModel.score))
        .setCancelable(false)
        .setNegativeButton(getString(R.string.exit)) { _, _ ->
            exitGame()
        }
        .setPositiveButton(getString(R.string.play_again)) { _, _ ->
            restartGame()
        }
        .show()

}

private fun restartGame() {
    viewModel.reinitializeData()
    setErrorTextField(false)

}

/*
 * Exits the game.
 */
private fun exitGame() {
    activity?.finish()
}

/*
* Sets and resets the text field error status.
*/
private fun setErrorTextField(error: Boolean) {
    if (error) {
        binding.textField.isErrorEnabled = true
        binding.textField.error = getString(R.string.try_again)
    } else {
        binding.textField.isErrorEnabled = false
        binding.textInputEditText.text = null
    }
}

/*
 * Displays the next scrambled word on screen.
 */



override fun onDetach() {
    super.onDetach()
    Log.d("GameFragment", "GameFragment destroyed!")
}
}
Run Code Online (Sandbox Code Playgroud)

Tyl*_*r V 7

Kotlin 中的初始化按照写入的顺序进行(从上到下)。因为您的init块是在初始化之前_currentScrambledWord列出的,所以当您尝试在 中使用它时,它为 null init。您应该将该init块移动到类定义的末尾,因此它至少位于 LiveData 之后,如下所示:

private var wordsList: MutableList<String> = mutableListOf()
private var currentWord: String = "" // doesn't have to be lateinit if you always set it in "init" - better yet, just put a default

private var _score = 0
val score: Int
    get() = _score

private var _currentWordCount = 0
val currentWordCount: Int
    get() = _currentWordCount

private var _currentScrambledWord = MutableLiveData<String>()
val currentScrambledWord: LiveData<String>
    get() = _currentScrambledWord

// other stuff

// init at the very bottom
init {
    Log.d("GameFragment", "GameViewModel created!")
    getNextWord()
}
Run Code Online (Sandbox Code Playgroud)

请查看此处了解一些其他背景信息。