kotlinandroid-jetpack-compose

Kotlin Compose: Flow don't observe data change


I want to create an interactive column. For that, it's necessary to observeto state. But the lazy column doesn't display change.

State:

data class TournamentState(
    var tournament:Tournament= Tournament(0,"",TounamentType.SWISS_ROUND, mutableStateListOf()),
    var tournamentScreen: TournamentScreen=TournamentScreen.PLAYERS
)

View model:

class TournamentViewModel(
    private val tournamentRepository: TournamentRepository,
    private val playerRepository: PlayerRepository
) : ViewModel(){
    private val _uiState = MutableStateFlow(TournamentState())
    val uiState: StateFlow<TournamentState> = _uiState.asStateFlow()

    fun initData(tournamentId:Int){
        viewModelScope.launch {
            _uiState.value.tournament=
                tournamentRepository.getStream(tournamentId).first()?.toModel() ?: returnDataTest()
        }
    }

    fun addUser(){
        _uiState.value.tournament.players.add(
            Player(
                maxId()+1,
                "Player"+(_uiState.value.tournament.players.size+1)
            )
        )
    }

    fun removeUser(player: Player){
        _uiState.value.tournament.players.remove(player)
    }

    fun changePage(tournamentScreen: TournamentScreen){
        _uiState.value=_uiState.value.copy(tournamentScreen = tournamentScreen)
    }

1st activity:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TounamentActivity(
    tournamentId:Int,
    tournamentViewModel: TournamentViewModel = viewModel(factory = AppViewModelProvider.factory)
){
    tournamentViewModel.initData(tournamentId)
    val tournamentState by tournamentViewModel.uiState.collectAsState()
    Scaffold (
        topBar = {
            TopAppBar(title = {
                Row(horizontalArrangement = Arrangement.SpaceEvenly,
                    modifier = Modifier.fillMaxWidth()) {
                    IconButton(onClick = { tournamentViewModel.changePage(TournamentScreen.PLAYERS) }) {
                        Icon(imageVector = Icons.Filled.AccountCircle, contentDescription = "Players", tint = Color.Black)
                    }
                    IconButton(onClick = { tournamentViewModel.changePage(TournamentScreen.DRAFT) }) {
                        Icon(imageVector = Icons.Filled.List, contentDescription = "Draft", tint = Color.Black)
                    }
                    IconButton(onClick = { tournamentViewModel.changePage(TournamentScreen.RESULT) }) {
                        Icon(imageVector = Icons.Filled.Star, contentDescription = "Result", tint = Color.Black)
                    }
                }
            }, colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Cyan))
        },
    ){ innerPadding->
        Card(modifier = with(Modifier){
                fillMaxSize()
                    .padding(innerPadding)
                    .paint(
                    painterResource(R.drawable.androidparty),
                    contentScale = ContentScale.Crop)
            }
            ) {
            tournamentState.tournamentScreen.let {
                when(it){
                    TournamentScreen.PLAYERS-> PlayersActivity(tournamentViewModel = tournamentViewModel)
                    TournamentScreen.DRAFT-> Text(text = "draft Not Implement")
                    TournamentScreen.RESULT-> Text(text = "result Not Implement")
                }
            }
        }
    }
}

2nd activity:

fun PlayersActivity(
    tournamentViewModel: TournamentViewModel
){
    val tournamentUiState by tournamentViewModel.uiState.collectAsState()
    Column (modifier = Modifier
        .fillMaxSize()){
        PlayersBase(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight(0.9f), itemsList = tournamentUiState.tournament.players
        ) { player: Player -> tournamentViewModel.removeUser(player) }
        Button(
            onClick = {
                tournamentViewModel.addUser()
            },
            modifier = Modifier
                .fillMaxSize()
                .padding(10.dp),
            shape = RoundedCornerShape(10.dp)
        ) {
            Image(painter = painterResource(R.drawable.plus), contentDescription = "Add player")
        }
    }
}


@Composable
fun PlayersBase(
    modifier: Modifier, itemsList: List<Player>, remove: (Player) -> Unit
) {
    LazyColumn(modifier = modifier
        .fillMaxSize()
        .padding(0.dp, 10.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp)){
        items(items = itemsList, key = {it.id}){
            PlayerCard(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(0.08f),
                player = it,
                remove = {
                    remove(it)
                }
            )
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayerCard(modifier: Modifier = Modifier,player: Player, remove:() -> Unit){
    var name by remember { mutableStateOf(player.name) }
    Card(modifier = modifier
        .fillMaxSize()
        .padding(20.dp, 0.dp),) {
        Row (modifier = Modifier.fillMaxSize()){
            TextField(value = name, onValueChange = {name=it},
                modifier= Modifier
                    .fillMaxHeight()
                    .fillMaxWidth(0.8f),
                textStyle = TextStyle.Default.copy(fontSize = 20.sp)
            )
            IconButton(onClick = { remove() }, modifier = Modifier.fillMaxSize()) {
                Icon(imageVector = Icons.Filled.Clear, contentDescription = "Remove player", tint = Color.Black)
            }
        }
    }
}

What is solution to force observe state's data? I don't know what I'm doing wrong so I have no clue. If you have any questions don't hesitate to ask.


Solution

  • Your problems are in addUser and removeUser functions because you modify the same instance of TournamentState so StateFlow still sees it as the same value

    fun addUser() {
        _uiState.value = _uiState.value.copy (
            tournament = _uiState.value.tournament.copy (
                players = _uiState.value.tournament.players.toMutableList().apply {
                    add(Player(maxId() + 1, "Player${_uiState.value.tournament.players.size + 1}"))
                }
            )
        )
    }
    
    fun removeUser(player: Player) {
        _uiState.value = _uiState.value.copy (
            tournament = _uiState.value.tournament.copy (
                players = _uiState.value.tournament.players.toMutableList().apply {
                    remove(player)
                }
            )
        )
    }