Jetpack Compose中ViewModel与导航的状态保留问题求助
我正在学习如何使用ViewModel在Jetpack Compose的不同页面间传递数据,编写了一个简单的示例程序。在PlayerSelection页面中,点击选择玩家数量的按钮后,MainViewModel中的howManyPlayers值可成功修改并正常显示,但导航至TwoPlayerGame页面后该值重置为0。
现有代码
MainActivity代码
sealed class Destination(val route: String) { object PlayerSelection: Destination("PlayerSelection") object TwoPlayerGame: Destination("TwoPlayerGame") object ThreePlayerGame: Destination("ThreePlayerGame") object FourPlayerGame: Destination("FourPlayerGame") } class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Viewmodels_and_NavigationTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { ScreenSetup(viewModel) } } } } }
NavigationAppHost代码
@Composable fun NavigationAppHost (navController: NavController, viewModel: MainViewModel) { NavHost(navController = navController as NavHostController, startDestination = "PlayerSelection") { composable(Destination.PlayerSelection.route) { PlayerSelection(navController,MainViewModel()) } composable(Destination.TwoPlayerGame.route) {TwoPlayerGame(navController,MainViewModel())} composable(Destination.ThreePlayerGame.route) {ThreePlayerGame(navController,MainViewModel())} composable(Destination.FourPlayerGame.route) {FourPlayerGame(navController,MainViewModel())} } }
ScreenSetup代码
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ScreenSetup(viewModel: MainViewModel) { val navController = rememberNavController() Scaffold( topBar = { TopAppBar(title = { Text(text = "Viewmodels and Navigation") }) }, content = { padding -> Column(Modifier.padding(padding)) { NavigationAppHost(navController = navController,viewModel) } }, bottomBar = { Text(text = "BottomBar") } ) }
PlayerSelection组件代码
@Composable fun PlayerSelection(navController: NavController, viewModel: MainViewModel) { val HowManyPlayers: Int by viewModel.howManyPlayers.collectAsState() Column { Row { Text(text = "How Many Players?") } Row() { Button(onClick = { viewModel.Selection(2) }) { Text("Two Players") } Button(onClick = { viewModel.Selection(3) }) { Text("Three Players") } Button(onClick = { viewModel.Selection(4) }) { Text("Four Players") } } Row() { Text(text = "Current Value Held in MainViewModel = ${HowManyPlayers}") } Button(onClick = { if (HowManyPlayers == 2) { navController.navigate(Destination.TwoPlayerGame.route) } if (HowManyPlayers == 3) { navController.navigate(Destination.ThreePlayerGame.route) } if (HowManyPlayers == 4) { navController.navigate(Destination.FourPlayerGame.route) } }) { Text(text = "Click To Navigate To Next Screen") } } }
MainViewModel代码
class MainViewModel : ViewModel() { var _howManyPlayers = MutableStateFlow(0) var howManyPlayers = _howManyPlayers.asStateFlow() fun Selection(players:Int) { _howManyPlayers.value = players } }
TwoPlayerGame组件代码
@Composable fun TwoPlayerGame(navController: NavController, viewModel: MainViewModel) { val howManyPlayers by viewModel.howManyPlayers.collectAsState() Column { Row(Modifier.padding(50.dp)) { Text(text = "Two Player Game") } Row { Text(text = "Value of howManyPlayers in viewmodel = ${howManyPlayers}") } } }
问题原因
你在NavigationAppHost的每个composable路由中,都手动创建了新的MainViewModel()实例(比如PlayerSelection(navController,MainViewModel()))。这意味着每个页面使用的都是独立的ViewModel对象,而非同一个实例。当你在PlayerSelection页面修改的是第一个ViewModel实例的数据,导航到TwoPlayerGame页面时,使用的是全新的ViewModel,其howManyPlayers初始值为0,所以显示重置了。
解决方法
要实现跨页面共享ViewModel数据,需要确保所有页面使用同一个ViewModel实例。具体修改如下:
修改NavigationAppHost代码
将每个页面组件中新建MainViewModel()的代码,替换为传入的viewModel参数:
@Composable fun NavigationAppHost (navController: NavController, viewModel: MainViewModel) { NavHost(navController = navController as NavHostController, startDestination = "PlayerSelection") { composable(Destination.PlayerSelection.route) { PlayerSelection(navController, viewModel) } composable(Destination.TwoPlayerGame.route) { TwoPlayerGame(navController, viewModel) } composable(Destination.ThreePlayerGame.route) { ThreePlayerGame(navController, viewModel) } composable(Destination.FourPlayerGame.route) { FourPlayerGame(navController, viewModel) } } }
补充说明
你的MainViewModel是在MainActivity中通过by viewModels()创建的,其作用域绑定到Activity生命周期。通过将这个实例层层传递给导航图中的所有页面组件,就能保证所有页面共享同一ViewModel的状态数据,修改后的数据会在所有订阅的页面中同步更新。
内容的提问来源于stack exchange,提问作者OldDogNewTricks

