我试图了解如何使用 ViewModel 来保存可以传递到不同屏幕的值。
我编写了一个非常简单的程序来尝试理解这个概念。
这是我的主要活动:
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())}
}
}
这是我的屏幕设置:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScreenSetup(viewModel: MainViewModel) {
//Navigation Stuff
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") }
)
}
这是可组合的播放器选择:
@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}")
}
}
}
当用户在 PlayerSelection 屏幕中选择多少个玩家时,MainViewModel 中的 HowManyPlayers 变量会根据用户按下的按钮而成功更改。我可以看到这与以下行一起工作:
Text(text = "Current Value Held in MainViewModel = ${HowManyPlayers}")
这太酷了。但是,当我单击按钮导航到 TwoPlayerGame 时,MainViewModel 中的变量 HowManyPlayers 再次重置为 0。为什么在导航到新屏幕时 MainViewModel 中的 HowManyPlayers 值重置为 0?有没有办法将 ViewModel 传递到不同的屏幕,同时保留传递的 ViewModel 中的变量值?
感谢您考虑我的问题,我真的很感激!
有两种共享数据的方式。
首先,您可以使用导航参数来传递参数。传递一些值并在屏幕上获取值。 https://developer.android.com/jetpack/compose/navigation#nav-with-args
其次,使用存储库层来共享一些数据。公开存储库中的流并在 ViewModel 中收集该值。然后,通过存储库中的流程发出一些数据。 https://developer.android.com/topic/architecture/data-layer
也许,您可以通过修复
NavigationAppHost
函数以传递 viewModel
来与一个 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)}
}
}
AAC(Android Architecture Component)建议每个屏幕使用一个 ViewModel。因为它保存特定的 UI 状态,当发生配置更改时可以销毁该状态。但是,MVVM 模式还有另一种观点,例如在多个屏幕上重用 ViewModel。 您可以参考AAC ViewModel, MVVM 视图模型
以下是使用 Jetpack Compose 学习 ViewModel 和导航的好示例。
https://github.com/android/socialite
https://github.com/android/nowinandroid [高级]