简体   繁体   中英

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") } . But what happens if I have to trigger navigation event from ViewModel (ex. redirect on login, redirect to newly created post page)? 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

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.

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:

@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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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