尝试调用虚拟方法' void androidx.lifecycle.mutableLivedata.setValue(java.lang.object)'在空对象引用(Android Kotlin)上

发布于 2025-02-12 20:03:43 字数 6638 浏览 0 评论 0 原文

很抱歉长标题,但是我不确定该错误在我的代码中处于何处,我确实怀疑该错误在于实现 livedata and 观察>观察

我正在使用的应用程序是一个无流行的单词应用程序,用户必须在片段上显示字母。到目前为止,我的代码包括下面列出的2个Kotlin类, fragment 和a viewModel 类。

我目前已经分配了变量 _currentscrambledword 作为 mutableLivedata< string>(),并在 view> view> view> viewmodel.kt

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

我尝试附加尝试附加 的备份属性使用以下代码的观察者 gamefragment

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

据我了解, livedata 简化了从 viewModel 减少所需代码量的过程。

这是我在logcat中发现的错误,

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

如下所示,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)

viewmodel.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()
}





}

gamefragment.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!")
}
}

Sorry for the long title, however, I am unsure where this error is at in my code, however, I do suspect the error lies in the implementation of the liveData and Observation.

The app which I am working on is an Unscrambler word app where users have to unscramble the letters displayed on the fragment. My code so far consists of 2 Kotlin classes listed below, a fragment and a ViewModel class.

I have currently assigned the variable _currentScrambledWord as a MutableLiveData<String>() and utilised the backing property in ViewModel.kt

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

I then tried to attach an observer to the GameFragment using the code below.

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

From what I understand the LiveData simplifies the process of retrieving data from the ViewModel reducing the amount of code needed.

Here is the error I found in Logcat

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

The error lines in the logcat is as follows

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)

Viewmodel.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()
}





}

GameFragment.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!")
}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

愁杀 2025-02-19 20:03:43

Kotlin中的初始化以书面的顺序(上到底)发生。因为您的 init 块在之前列出了 您初始化 _CurrentsCrambledword ,因此尝试在 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()
}

在这里 在某些其他上下文中。

Initialization in Kotlin happens in the order written (top to bottom). Because your init block is listed before you initialize _currentScrambledWord, it is null when you try to use it in init. You should move the init block to the end of the class definition, so it is at least after the LiveData, something like this:

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()
}

Have a look here for some additional context.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文