繁体   English   中英

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

[英]What is the proper way to navigate from ViewModel in Jetpack Compose + Hilt + ViewModel?

我偶然发现了这个非常微不足道但很棘手的问题。 我花了相当多的时间搜索官方文档,但不幸的是没有找到答案。

官方文档说您应该将NavController的实例传递给@Composable -s,并将其称为onClick = { navController.navigate("path") } 但是,如果我必须从 ViewModel 触发导航事件(例如登录时重定向、重定向到新创建的帖子页面)会发生什么? @Composable中等待任何协程(例如 HTTP 请求)不仅不好,而且可能会因为阻塞的 UI 线程而迫使 Android 杀死应用程序

非官方解决方案(主要以 Medium 文章的形式记录)基于具有单例类并观察一些MutableStateFlow包含路径的概念。

这在理论上听起来很愚蠢,在实践中并没有多大帮助(对副作用和重组不友好,会触发不必要的重新导航)。

rememberNavController有一个非常简单的源代码,您可以使用它在单例服务中创建它:

@Singleton
class NavigationService @Inject constructor(
    @ApplicationContext context: Context,
) {
    val navController = NavHostController(context).apply {
        navigatorProvider.addNavigator(ComposeNavigator())
        navigatorProvider.addNavigator(DialogNavigator())
    }
}

创建一个辅助视图模型以与NavHost视图共享NavHostController

@HiltViewModel
class NavViewModel @Inject constructor(
    navigationService: NavigationService,
): ViewModel() {
    val controller = navigationService.navController
}

NavHost(
    navController = hiltViewModel<NavViewModel>().controller,
    startDestination = // ...
) {
    // ...
}

然后在任何视图模型中,您都可以将其注入并用于导航:

@HiltViewModel
class ScreenViewModel @Inject constructor(
    private val navigationService: NavigationService
): ViewModel() {
    fun navigateToNextScreen() {
        navigationService.navController.navigate(Destinations.NextScreen)
    }
}

我自己也一直在为同样的问题而苦苦挣扎。 从谷歌提供的关于这个主题的有限文档, 特别是架构事件部分,我想知道他们是否建议使用状态作为导航的触发器?

引用文件:

例如,在实现登录屏幕时,点击登录按钮应该会导致您的应用显示进度微调器和网络调用。 如果登录成功,那么您的应用会导航到另一个屏幕; 如果出现错误,应用程序会显示 Snackbar。 以下是对屏幕状态和事件建模的方法:

他们为上述要求提供了以下代码片段:

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
}

他们没有提供的是其余的视图模型和编写代码。 我猜它应该看起来像:

@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(...)  
    }
}

视图模型也可以具有这样的功能(通过单击撰写屏幕上的“登录”按钮触发

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM