Jetpack Compose中ViewModel空指针异常问题求助
问题:Jetpack Compose ViewModel运行时触发NullPointerException
运行应用时出现如下错误:
Attempt to invoke interface method 'boolean java.util.Set.contains(java.lang.Object)' on a null object reference
java.lang.NullPointerException: Attempt to invoke interface method 'boolean java.util.Set.contains(java.lang.Object)' on a null object reference
at com.example.android.ui.GameViewModel.pickRandomWordAndShuffle(GameViewModel.kt:21)
at com.example.android.ui.GameViewModel.(GameViewModel.kt:10)
对应的ViewModel代码:
import androidx.lifecycle.ViewModel import com.example.android.unscramble.data.allWords import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class GameViewModel : ViewModel() { private val _uiState = MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle())) val uiState: StateFlow<GameUiState> = _uiState.asStateFlow() private var _count = 0 val count: Int get() = _count private lateinit var currentWord: String private var usedWords: MutableSet<String> = mutableSetOf() private fun shuffleCurrentWord(word: String): String { val tempWord = word.toCharArray() // Scramble the word tempWord.shuffle() while (String(tempWord) == word) { tempWord.shuffle() } return String(tempWord) } private fun pickRandomWordAndShuffle(): String { // Continue picking up a new random word until you get one that hasn't been used before currentWord = allWords.random() if (usedWords.contains(currentWord)) { return pickRandomWordAndShuffle() } else { usedWords.add(currentWord) return shuffleCurrentWord(currentWord) } } private fun resetGame() { usedWords.clear() _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle()) } init { resetGame() } }
代码无编译错误,需解决该运行时异常。
问题原因与修复方案
原因分析
Kotlin类的初始化顺序是先按声明顺序初始化属性,再执行init代码块。这里的核心问题:
- 初始化
_uiState属性时,直接调用了pickRandomWordAndShuffle()方法 - 此时
usedWords属性还未完成初始化(声明顺序在_uiState之后),导致usedWords为null,调用contains()方法触发空指针异常。
修复方案
方案一:调整属性声明顺序
将usedWords的声明移到_uiState之前,确保它先完成初始化:
class GameViewModel : ViewModel() { // 先声明usedWords,保证初始化顺序优先级 private var usedWords: MutableSet<String> = mutableSetOf() private lateinit var currentWord: String private val _uiState = MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle())) val uiState: StateFlow<GameUiState> = _uiState.asStateFlow() // 其余代码保持不变 // ... }
方案二:延迟初始化_uiState,在init块中完成配置
把_uiState改为lateinit,在init块中通过resetGame()完成初始化,确保所有依赖属性都已准备就绪:
class GameViewModel : ViewModel() { private lateinit var _uiState: MutableStateFlow<GameUiState> val uiState: StateFlow<GameUiState> get() = _uiState.asStateFlow() private var _count = 0 val count: Int get() = _count private lateinit var currentWord: String private var usedWords: MutableSet<String> = mutableSetOf() // 其余方法保持不变... init { resetGame() } private fun resetGame() { usedWords.clear() // 在此处初始化_uiState _uiState = MutableStateFlow(GameUiState(currentScrambledWord = pickRandomWordAndShuffle())) } }
额外优化建议
- 避免在属性初始化阶段调用业务逻辑方法,尽量把初始化逻辑集中到
init块或专用初始化方法中,降低初始化顺序问题的概率 - 给
usedWords添加private set修饰,限制外部修改,增强代码封装性
内容的提问来源于stack exchange,提问作者Monibur Haque
相关产品推荐
相关产品推荐

