Why do I get 'Failed to instantiate a ViewModel' when passing ViewModel class as an argument to a composable?

19 Views Asked by At

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.

Error message

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.

1

There are 1 best solutions below

0
devel_dizzie On

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

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

To

class GameViewModel : ViewModel() {

// Game UI state
private val _uiState = MutableStateFlow(GameUiState())
// Set of words used in the game
private var usedWords: MutableSet<String> = mutableSetOf()
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())
}

Did the trick.