8

I am using jetpack compose with the navigation compose library to navigate from one screen to the next. Usually you would have a ViewModel that would take care of user interactions (e.g.: viewModel.addItem()). In order to fulfill the addItem command i would like to show another screen via navController.navigate(). The ViewModel itself is injected into the Composable via hiltNavGraphViewModel().

Now the question is: How could i inject the NavController into the ViewModel via hilt?

@HiltViewModel
class ScreenViewModel @Inject constructor(
  private val navController: NavController // where does it come from?
) : ViewModel() {

  fun addItem() {
    navController.navigate("add-item-screen")
  }

}

The NavController is created via the rememberNavController() method up in the composable hirarchy. I also do not want to pass the controller down the composable hierarchy or use a CompositionLocal. The preferred approach would be to have the controller available in the ViewModel.

Moritz
  • 10,124
  • 7
  • 51
  • 61
  • put callback on `Compose function Screen` and don't bring `NavController` into `ViewModel`. I never see that on compose or not. – Rofie Sagara May 04 '21 at 11:57
  • and make the view model use StateFlow for event. let that collect on `Screen` dan call the callback function – Rofie Sagara May 04 '21 at 11:58
  • @RofieSagara thanks for the answer. I have attempted to use the a StateFlow as an event but it has several problems. It is not in synch when back is pressed. It is not easy to check what that we do not need to navigate again when already on target route. the currentDestination.hasDeepLink() method does not allow to create a corresponding route URI. – Moritz May 04 '21 at 12:24
  • @Moritz what did you end up doing for this? did you pull the navcontroller out of the viewmodel or were you able to figure out how to inject it? – user1809913 Mar 28 '22 at 17:35

1 Answers1

0

if you want to inject it to viewmodel, you should create navigation controller using application context, install it in application (singleton) component and mark it as singleton:

@Module
@InstallIn(SingletonComponent::class)
object NavigationModule {

    @Provides
    @Singleton
    fun provideNavController(@ApplicationContext context: Context) = NavHostController(context).apply {
        navigatorProvider.addNavigator(ComposeNavigator())
        navigatorProvider.addNavigator(DialogNavigator())
    }
}

but in my opinion this is not good practise

Alexander
  • 9
  • 1
  • 3
    why souldn't it be? This way the view models have all the logic related to button clicks for example, and control the navigation since they have the state and can control what they pass/receive from other screens. this way the views are only ui related and don't have navigation logic. what do you think? – Gonçalo Gaspar Oct 24 '22 at 22:28