Home > Blockchain >  Button onClick keep replacing values in mutableStateList
Button onClick keep replacing values in mutableStateList

Time:04-02

I'm trying to display a 4x4 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:

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
    )
}

CodePudding user response:

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
    )
}
  • Related