Following this tutorial. I create the ViewModel and want to expose it to the GameScreen () composable. I keep getting the error 'Failed to instantiate a ViewModel', the preview fails to render and the app keeps crashing in the emulator.
I know this is legal because the solution code works! SolutionCode
Composable:
@Composable
fun GameScreen(
gameViewModel: GameViewModel = viewModel()
) {
val gameUiState by gameViewModel.uiState.collectAsState()
/*
rest of composable
*/
This is the ViewModel class (GameViewModel.kt):
package com.example.unscramble.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.example.unscramble.data.allWords
/*viewmodel contains functionality of the app picking words, shuffling, resetting
* is immutable to other classes (read-only)
* */
class GameViewModel : ViewModel() {
// Game UI state
private val _uiState = MutableStateFlow(GameUiState())
var userGuess by mutableStateOf("")
private set
// Backing property to avoid state updates from other classes
val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()
init {
resetGame()
}
fun resetGame() {
usedWords.clear()
_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
}
private lateinit var currentWord: String
// Set of words used in the game
private var usedWords: MutableSet<String> = mutableSetOf()
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 shuffleCurrentWord(word: String): String {
val tempWord = word.toCharArray()
// Scramble the word
tempWord.shuffle()
while (String(tempWord).equals(word)) {
tempWord.shuffle()
}
return String(tempWord)
}
}
I have edited the dependencies from projects that use ViewModels like this
current configuration
dependencies {
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.activity:activity-compose:1.8.0")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0")
I have narrowed down the problem to these two lines in GameViewModel.kt
init {
resetGame()
}
fun resetGame() {
usedWords.clear()
_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())
}
If I comment out the lines in resetGame(). The viewmodel gets instantiated i.e works and the preview renders. If I uncomment "usedWords.clear()" or "_uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle())" the viewmodel fails to be instantiated.
Why do these two lines prevent the viewmodel's instantiation? Continuing with the lines commented out but will need to add this functionality eventually.
Very silly, moving the var declaration for 'usedWords' above the function definitions got rid of my error. Must be breaking some syntax rules making the 'GameViewModel.kt' class illegal/broken.
Changing
class GameViewModel : ViewModel() {
To
Did the trick.