[英]What is the proper way to navigate from ViewModel in Jetpack Compose + Hilt + ViewModel?
I have stumbled upon this quite trivial, but tricky problem.我偶然发现了这个非常微不足道但很棘手的问题。 I have spent a decent amount of time searching official docs, but unfortunately found no answer.我花了相当多的时间搜索官方文档,但不幸的是没有找到答案。
Official docs say that you should pass an instance of NavController
down to @Composable
-s, and call it as onClick = { navController.navigate("path") }
.官方文档说您应该将NavController
的实例传递给@Composable
-s,并将其称为onClick = { navController.navigate("path") }
。 But what happens if I have to trigger navigation event from ViewModel (ex. redirect on login, redirect to newly created post page)?但是,如果我必须从 ViewModel 触发导航事件(例如登录时重定向、重定向到新创建的帖子页面)会发生什么? Awaiting any coroutine (ex. HTTP request) in @Composable
would be not just bad, but probably force Android to kill app because of the blocked UI thread在@Composable
中等待任何协程(例如 HTTP 请求)不仅不好,而且可能会因为阻塞的 UI 线程而迫使 Android 杀死应用程序
Unofficial solutions (documented mostly if form of Medium articles) are based on the concept of having a singleton class and observing some MutableStateFlow
containing path.非官方解决方案(主要以 Medium 文章的形式记录)基于具有单例类并观察一些MutableStateFlow
包含路径的概念。
That sounds stupid in theory, and doesn't help much in practice (not side-effect and recomposition friendly, triggers unnecessary re-navigation).这在理论上听起来很愚蠢,在实践中并没有多大帮助(对副作用和重组不友好,会触发不必要的重新导航)。
The rememberNavController
has a pretty simple source code that you can use to create it in a singleton service: rememberNavController
有一个非常简单的源代码,您可以使用它在单例服务中创建它:
@Singleton
class NavigationService @Inject constructor(
@ApplicationContext context: Context,
) {
val navController = NavHostController(context).apply {
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
}
Create a helper view model to share NavHostController
with NavHost
view:创建一个辅助视图模型以与NavHost
视图共享NavHostController
:
@HiltViewModel
class NavViewModel @Inject constructor(
navigationService: NavigationService,
): ViewModel() {
val controller = navigationService.navController
}
NavHost(
navController = hiltViewModel<NavViewModel>().controller,
startDestination = // ...
) {
// ...
}
Then in any view model you can inject it and use for navigation:然后在任何视图模型中,您都可以将其注入并用于导航:
@HiltViewModel
class ScreenViewModel @Inject constructor(
private val navigationService: NavigationService
): ViewModel() {
fun navigateToNextScreen() {
navigationService.navController.navigate(Destinations.NextScreen)
}
}
I have been struggling with the exact same question myself.我自己也一直在为同样的问题而苦苦挣扎。 From the limited documentation Google provided on this topic, specifically the architecture events section I'm wondering if what they're suggesting is to use a state as a trigger for navigation?从谷歌提供的关于这个主题的有限文档, 特别是架构事件部分,我想知道他们是否建议使用状态作为导航的触发器?
Quoting the document:引用文件:
For example, when implementing a sign-in screen, tapping on a Sign in button should cause your app to display a progress spinner and a network call.例如,在实现登录屏幕时,点击登录按钮应该会导致您的应用显示进度微调器和网络调用。 If the login was successful, then your app navigates to a different screen;如果登录成功,那么您的应用会导航到另一个屏幕; in case of an error the app shows a Snackbar.如果出现错误,应用程序会显示 Snackbar。 Here's how you would model the screen state and the event:以下是对屏幕状态和事件建模的方法:
They have provided the following snippet of code for the above requirement:他们为上述要求提供了以下代码片段:
sealed class UiState {
object SignedOut : UiState()
object InProgress : UiState()
object Error : UiState()
object SignIn : UiState()
}
class MyViewModel : ViewModel() {
private val _uiState = mutableStateOf<UiState>(SignedOut)
val uiState: State<UiState>
get() = _uiState
}
What they did not provide is the rest of the view model and compose code.他们没有提供的是其余的视图模型和编写代码。 I'm guessing it's supposed to look like:我猜它应该看起来像:
@Composable
fun MyScreen(navController: NavController, viewModel: MyViewModel) {
when(viewModel.uiState){
is SignedOut -> // Display signed out UI components
is InProgress -> // Display loading spinner
is Error -> // Display error toast
// Using the SignIn state as a trigger to navigate
is SignIn -> navController.navigate(...)
}
}
Also the view model could have a function like this one (trigger by clicking a "sign in" button from compose screen视图模型也可以具有这样的功能(通过单击撰写屏幕上的“登录”按钮触发
fun onSignIn() {
viewModelScope.launch {
// Make a suspending sign in network call
_uiState.value = InProgress
// Trigger navigation
_uiState.value = SignIn
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.