Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Button onClick keep replacing values in mutableStateList

I’m trying to display a 4×4 grid with values that change depending on user input. To achieve that, I created mutableStateListOf that I use in a ViewModel to survive configuration changes. However, when I try to replace a value in that particular list using button onClick, it keeps doing that until app crashes. I can’t understand why is onReplaceGridContent looping after clicking the button once. Currently, my code looks like this:

ViewModel:

class GameViewModel : ViewModel(){

    var gameGridContent = mutableStateListOf<Int>()
        private set // Restrict writes to this state object to private setter only inside view model

    fun replaceGridContent(int: Int, index: Int){
        gameGridContent[index] = int
    }

    fun removeGridContent(index: Int){
        gameGridContent[index] = -1
    }

    fun initialize(){
        for(i in 0..15){
            gameGridContent.add(-1)
        }

        val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
        val firstGridNumber = GameUtils.getRandomTileNumber()

        gameGridContent[firstEmptyGridTile] = firstGridNumber
    }
}

Button:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Button(
  onClick = { 
      onReplaceGridContent(GameUtils.getRandomTileNumber(),GameUtils.getRandomTilePosition(gameGridContent))},
      colors = Color.DarkGray
    ){
      Text(text = "Add number to tile")
    }

Activity Composable:

@Composable
fun gameScreen(gameViewModel: GameViewModel){
    gameViewModel.initialize()
    MainStage(
        gameGridContent = gameViewModel.gameGridContent,
        onReplaceGridContent = gameViewModel::replaceGridContent,
        onRemoveGridContent = gameViewModel::removeGridContent
    )
}

>Solution :

Your initialize will actually run on every recomposition of gameScreen:

  1. You click on a tile – state changes causing recomposition.
  2. initializa is called and changes the state again causing recomposition.
  3. Step 2 happens again and again.

You should initialize your view model in its constructor instead (or use boolean flag to force one tim initialization) to make it inly once.

Simply change it to constructor:

class GameViewModel : ViewModel(){

    var gameGridContent = mutableStateListOf<Int>()
        private set // Restrict writes to this state object to private setter only inside view model

    fun replaceGridContent(int: Int, index: Int){
        gameGridContent[index] = int
    }

    fun removeGridContent(index: Int){
        gameGridContent[index] = -1
    }

    init {
        for(i in 0..15){
            gameGridContent.add(-1)
        }

        val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
        val firstGridNumber = GameUtils.getRandomTileNumber()

        gameGridContent[firstEmptyGridTile] = firstGridNumber
    }
}

Now you don’t need to call initialize in the composable:

@Composable
fun gameScreen(gameViewModel: GameViewModel){
    MainStage(
        gameGridContent = gameViewModel.gameGridContent,
        onReplaceGridContent = gameViewModel::replaceGridContent,
        onRemoveGridContent = gameViewModel::removeGridContent
    )
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading