Search code examples
androidkotlinandroid-jetpack-composeandroid-viewmodelkotlin-stateflow

Why doesn't my UiState(stateflow object) update after updating in the viewmodel?


I update the uiState in the model by the updateSelectedCategory(category = selectedCategory) function, I checked through the logs int the viewmodel, everything is fine, it is updated. But, when I check this in the WhehereToGoApp compose function, the parameter uiState.currentPlacesList is not updated, why? I think I'm somehow not collecting the state correctly, but I can't figure out how to fix it.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WhereToGoApp() {
    val viewModel = WhereToGoViewModel()
    val uiState = viewModel.uiState.collectAsState()
    val navController = rememberNavController()

    val isCategoryScreen = (navController.currentBackStackEntryAsState().value?.destination?.route
        ?: WhereToGoAppScreens.Categories.name) == WhereToGoAppScreens.Categories.name

    Scaffold(
        topBar = {
            WhereToGoAppBar( // todo
                title = if (isCategoryScreen) {
                    "Select Category "
                } else {
                    uiState.value.selectedCategory.title
                },
                showNavigationIcon = !isCategoryScreen,
                onBackButtonClick = {
                    navController.navigateUp()
                }
            )
        }
    ) { contentPadding ->
        NavHost(
            navController = navController,
            startDestination = WhereToGoAppScreens.Categories.name
        ) {
            composable(route = WhereToGoAppScreens.Categories.name) {
                CategoriesScreen(
                    categoryList = LocalDataProvider.getCategories(),
                    onItemClick = { selectedCategory ->
                        viewModel.updateSelectedCategory(category = selectedCategory)
                        navController.navigate(WhereToGoAppScreens.Places.name)
                    },
                    paddingValues = contentPadding
                )
            }
            composable(route = WhereToGoAppScreens.Places.name) {
                PlacesScreen(
                    places = uiState.value.currentPlacesList,
                    onItemClick = { selectedPlace ->
                        viewModel.updateSelectedPlace(place = selectedPlace)
                        navController.navigate(WhereToGoAppScreens.PlaceDetails.name)
                    },
                    paddingValues = contentPadding
                )
            }
            composable(route = WhereToGoAppScreens.PlaceDetails.name) {
                PlaceDetailScreen(
                    place = uiState.value.selectedPlace,
                    paddingValues = contentPadding
                )
            }
        }
    }
}

ViewModel code

class WhereToGoViewModel : ViewModel() {

    private val _uiState = MutableStateFlow(WhereToGoUiState())
    val uiState: StateFlow<WhereToGoUiState> = _uiState.asStateFlow()

    fun updateSelectedCategory(category: Category) {
        _uiState.update { uiState ->
            uiState.copy(
                selectedCategory = category,
                currentPlacesList = LocalDataProvider.getPlaces().filter {
                    it.category == category
                }
            )
        }
    }

    fun updateSelectedPlace(place: Place) {
        _uiState.update {
            _uiState.value.copy(
                selectedPlace = place
            )
        }
    }

}

data class WhereToGoUiState(
    val selectedCategory: Category = Category.NightClub,
    val currentPlacesList: List<Place> = emptyList(),
    val selectedPlace: Place = LocalDataProvider.getPlaces()[0],
)

Solution

  • The problem is that you recreate the view model every time your screen recomposes.

    val viewModel = WhereToGoViewModel()
    

    After you update the state recomposition happens. So your WhereToGoApp gets, roughly speaking, executed again, it recreates the view model and fetches the state from there. Since the new instance of the view model does not know anything about previous state updates, it simply returns the initial value.