简体   繁体   English

从 Jetpack Compose + Hilt + ViewModel 中的 ViewModel 导航的正确方法是什么?

[英]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.

相关问题 在视图模型中执行协程作业时,在jetpack compose中的屏幕之间导航的正确方法是什么? - What is the proper way to navigate between screens in jetpack compose while there's coroutines jobs to be executed in the viewmodel? 如何将参数从 Jetpack Compose 传递到我的刀柄视图模型 - How to pass parameter to my hilt viewmodel from jetpack compose jetpack compose viewModel() 给出错误“没有零参数构造函数”,带有刀柄 - jetpack compose viewModel() is giving error “has no zero argument constructor” with hilt 请帮助 - Jetpack Compose 测试 - Hilt - ViewModel - Repository - Please help - Jetpack Compose Testing - Hilt - ViewModel - Repository ViewModel 和 Jetpack Compose - ViewModel and Jetpack Compose Jetpack 使用 viewModel 组合导航 - Jetpack compose navigation with viewModel 从活动中共享视图模型以使用刀柄编写 function - Share viewmodel from activity to compose function using hilt Jetpack Compose 如何使用 savedStateHandle 在带有 Hilt ViewModel 的屏幕之间传递 arguments? - Jetpack Compose How to pass arguments between screens with Hilt ViewModel using savedStateHandle? Jetpack Compose + Hilt:java.lang.RuntimeException:无法创建 class ViewModel 的实例 - Jetpack Compose + Hilt: java.lang.RuntimeException: Cannot create an instance of class ViewModel Jetpack Compose 将参数传递给 viewModel - Jetpack Compose pass parameter to viewModel
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM