Has*_*san 43 android-viewmodel android-jetpack-navigation android-jetpack-compose
谁能建议如何在 Jetpack Compose Navigation 的不同部分中共享 ViewModel?
根据文档,viewModel 通常应该使用活动范围在不同的 compose 函数中共享,但如果在导航内部则不然。
这是我试图修复的代码。看起来我在导航内的两个部分中获得了两个不同的 viewModel:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationSystem()
}
}
}
@Composable
fun NavigationSystem() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("result") { ResultScreen(navController) }
}
}
@Composable
fun HomeScreen(navController: NavController) {
val viewModel: ConversionViewModel = viewModel()
var temp by remember { mutableStateOf("") }
val fahrenheit = temp.toIntOrNull() ?: 0
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column {
OutlinedTextField(
value = temp,
onValueChange = { temp = it },
label = { Text("Fahrenheit") },
modifier = Modifier.fillMaxWidth(0.85f)
)
Spacer(modifier = Modifier.padding(top = 16.dp))
Button(onClick = {
Log.d("HomeScreen", fahrenheit.toString())
if (fahrenheit !in 1..160) return@Button
viewModel.onCalculate(fahrenheit)
navController.navigate("result")
}) {
Text("Calculate")
}
}
}
}
@Composable
fun ResultScreen(navController: NavController) {
val viewModel: ConversionViewModel = viewModel()
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Log.d("ResultScreenDebug", "celsius: ${ viewModel.celsius.value.toString()}")
Text(
viewModel.celsius.value.toString(),
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.padding(top = 24.dp))
Button(onClick = { navController.navigate("home") }) {
Text(text = "Calculate again")
}
}
}
Run Code Online (Sandbox Code Playgroud)
调试日志:
2021-07-27 22:01:52.542 27113-27113/com.example.navigation D/ViewModelDebug: fh: 65, cs: 18, celcius: 18.0
2021-07-27 22:01:52.569 27113-27113/com.example.navigation D/ResultScreenDebug: celsius: 0.0
Run Code Online (Sandbox Code Playgroud)
谢谢!
akh*_*ris 27
考虑将您的 Activity 作为 viewModelStoreOwner 参数传递给 viewModel() fun,因为 ComponentActivity 实现了 ViewModelStoreOwner 接口:
val viewModel: ConversionViewModel = viewModel(LocalContext.current as ComponentActivity)
Run Code Online (Sandbox Code Playgroud)
此代码将在您的所有目标中返回 ConversionViewModel 的相同实例。
Gee*_*ers 21
您可以创建一个 viewModel 并将其传递给
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationSystem()
}
}
}
@Composable
fun NavigationSystem() {
val navController = rememberNavController()
val viewModel: ConversionViewModel = viewModel()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController, viewModel) }
composable("result") { ResultScreen(navController, viewModel) }
}
}
@Composable
fun HomeScreen(navController: NavController, viewModel: ConversionViewModel) {
var temp by remember { mutableStateOf("") }
val fahrenheit = temp.toIntOrNull() ?: 0
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column {
OutlinedTextField(
value = temp,
onValueChange = { temp = it },
label = { Text("Fahrenheit") },
modifier = Modifier.fillMaxWidth(0.85f)
)
Spacer(modifier = Modifier.padding(top = 16.dp))
Button(onClick = {
Log.d("HomeScreen", fahrenheit.toString())
if (fahrenheit !in 1..160) return@Button
viewModel.onCalculate(fahrenheit)
navController.navigate("result")
}) {
Text("Calculate")
}
}
}
}
@Composable
fun ResultScreen(navController: NavController, viewModel: ConversionViewModel) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Log.d("ResultScreenDebug", "celsius: ${ viewModel.celsius.value.toString()}")
Text(
viewModel.celsius.value.toString(),
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.padding(top = 24.dp))
Button(onClick = { navController.navigate("home") }) {
Text(text = "Calculate again")
}
}
}
Run Code Online (Sandbox Code Playgroud)
ch4*_*4uw 13
ViewModel我认为比将您的范围限制为整个范围更好的解决方案是在路线中NavGraph构建,然后从路线访问(路线范围):ViewModelHomeResult
//extensions
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry?.viewModel(): T? = this?.let {
viewModel(viewModelStoreOwner = it)
}
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.viewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}
): T {
return androidx.lifecycle.viewmodel.compose.viewModel(
viewModelStoreOwner = viewModelStoreOwner, key = T::class.java.name
)
}
//use-case
@Composable
fun HomeScreen(navController: NavController) {
val viewModel: ConversionViewModel = viewModel()
...
}
@Composable
fun ResultScreen(navController: NavController) {
val viewModel: ConversionViewModel? = navController.previousBackStackEntry.viewModel()
...
}
Run Code Online (Sandbox Code Playgroud)
但是,如果您必须将其范围扩展到整个NavGraph,您可以执行@akhris 所说的操作,但可以通过某种方式将 与ViewModelStoreOwner分开Activity:
//composable store-owner builder
@Composable
fun rememberViewModelStoreOwner(): ViewModelStoreOwner {
val context = LocalContext.current
return remember(context) { context as ViewModelStoreOwner }
}
Run Code Online (Sandbox Code Playgroud)
这样你就可以将Activity与你的分离ViewModelStoreOwner,并且可以执行以下操作:
val LocalNavGraphViewModelStoreOwner =
staticCompositionLocalOf<ViewModelStoreOwner> {
TODO("Undefined")
}
@Composable
fun NavigationSystem() {
val navController = rememberNavController()
val vmStoreOwner = rememberViewModelStoreOwner()
CompositionLocalProvider(
LocalNavGraphViewModelStoreOwner provides vmStoreOwner
) {
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("result") { ResultScreen(navController) }
}
}
}
@Composable
fun HomeScreen(navController: NavController) {
val viewModel: ConversionViewModel = viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current)
...
}
@Composable
fun ResultScreen(navController: NavController) {
val viewModel: ConversionViewModel = viewModel(viewModelStoreOwner = LocalNavGraphViewModelStoreOwner.current)
...
}
Run Code Online (Sandbox Code Playgroud)
推荐的方法,如果您想访问导航路线或导航图范围内的 ViewModel(即在导航路线或导航图之间共享),您应该使用:
@Composable
fun MyApp() {
val navController = rememberNavController()
val startRoute = "example"
val innerStartRoute = "exampleWithRoute"
NavHost(navController, startDestination = startRoute) {
navigation(startDestination = innerStartRoute, route = "Parent") {
composable("exampleWithRoute") { backStackEntry ->
//IMPORTANT PART: getting the scoped ViewModel reference.
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry("Parent")
}
val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
ExampleWithRouteScreen(parentViewModel)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了方便起见,您可以使用以下可能有用的扩展函数:
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
navController: NavController,
): T {
val navGraphRoute = destination.parent?.route ?: return viewModel()
val parentEntry = remember(this){
navController.getBackStackEntry(navGraphRoute)
}
return viewModel(parentEntry)
}
Run Code Online (Sandbox Code Playgroud)
然后你可以简单地在你的可组合路由中调用:
val parentViewModel = backStackEntry.sharedViewModel<ParentViewModel>(navController)
Run Code Online (Sandbox Code Playgroud)
您可能还想观看Philipp Lackner 关于该主题的视频。
希望能帮助到你!
| 归档时间: |
|
| 查看次数: |
27784 次 |
| 最近记录: |