Search code examples
androidkotlinmvvmandroid-jetpack-composekotlin-coroutines

java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied


I am working on a very basic snake game using kotlin and jetpack compose. To make it more efficient, I thought of using coroutines, but I keep getting the above mentioned error.

Full project is shared at: https://github.com/tauqirnizami/SnakeGame

ViewModel is:

class DifficultSnakeViewModel : ViewModel() {
    /*Pair(length/y-coordinate, width/x-coordinate)*/
    var coordinates = mutableStateListOf(Pair(31, 14), Pair(32, 14), Pair(33, 14))
        private set

    private var gameGoing by mutableStateOf(true)

    var extraWallsCoordinates: List<Pair<Int, Int>>

    init {
        foodCoordinates = Pair(10, 9)
        score = 0L
        directions = mutableStateListOf(0) //Using a list instead of a single int to keep track when user changes directions too quickly while the viewModel is on delay (the one for speed controlling). Eg., if user enter up and suddenly left as well, the game earlier used to only register the latter command. Using a mutable list  would keep track of all the given commands that currently hasn't been acted on.
        giantFoodCoordinates = null
        giantFoodCounter = 1

        extraWallsCoordinates = walls()

        viewModelScope.launch(Dispatchers.Default) {
            delay(1000L) //This is to let the viewModel to setup properly before being used. I was getting error due to usage of state variable (probably "coordinates") before waiting for the viewModel to be able to initialize properly first
            while (gameGoing) {
                delay(if (score < 250) 500 - (score *2) else if(score<500) 1 else 0) //This controls the snake speed
                coordinatesUpdation() //This is used to update the snake's coordinates according to the direction, i.e., user input.
            }
        }
    }
//other code
}

As you can see, in easy and medium mode of the game, using a simple delay(550L) was enough to keep the error away, but it isn't working for the difficult level even if I increased the delay duration from 550 to 1000 or even 3000 millis.

My composable file is:

@Composable
fun DifficultDisplayGrid(
    modifier: Modifier,
    snakeViewModel: DifficultSnakeViewModel
) {
    val colors = difficultGenerateColorGrid(coordinates = snakeViewModel.coordinates, walls = snakeViewModel.extraWallsCoordinates) //This function returns a list of colours according to snake, food, etc's coordinates, which would be used to display the grid
    Column {
        ColorGrid(colors = colors, modifier = modifier) //snake game grid
        Text(text = "Score = $score")
    }

}

@Composable
fun DifficultScreen(
    modifier: Modifier = Modifier,
    snakeViewModel: DifficultSnakeViewModel = DifficultSnakeViewModel()
) {
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = modifier
    ) {
        DifficultDisplayGrid(modifier, snakeViewModel = snakeViewModel)

        Row(
            modifier = Modifier,
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center
        ) {
            Button(onClick = { directions.add(2) }) {
                Icon(imageVector = Icons.Filled.KeyboardArrowLeft, contentDescription = "Left")
            }
            Column {
                Button(onClick = { directions.add(0) }) {
                    Icon(imageVector = Icons.Filled.KeyboardArrowUp, contentDescription = "Up")
                }
                Spacer(modifier = Modifier.height(13.dp))
                Button(onClick = { directions.add(1) }) {
                    Icon(imageVector = Icons.Filled.KeyboardArrowDown, contentDescription = "Down")
                }
            }
            Button(onClick = { directions.add(3) }) {
                Icon(imageVector = Icons.Filled.KeyboardArrowRight, contentDescription = "Right")
            }
        }
    }
}
//other code

I tried to use delay() function, Thread.sleep(). Tried to use thread.sleep before the viewModelScope .launch line, tried to do it inside this scope. But nothing was working. From 1000millis to 50000 milis, no function or no duration seems to be working.


Solution

  • try this

    viewModelScope.launch(Dispatchers.Main) {
      //  delay(1000L) //This is to let the viewModel to setup properly before being used. I was getting error due to usage of state variable (probably "coordinates") before waiting for the viewModel to be able to initialize properly first
        while (gameGoing) {
            delay(if (score < 250) 500 - (score *2) else if(score<500) 1 else 0) //This controls the snake speed
            coordinatesUpdation()
        }
    }
    

    To understand more about the Dispachers you can read here -> https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=es-419#main-safety