JonasLevin
JonasLevin

Reputation: 2099

Jetpack compose how to centralize navController and inject it

I currently have two NavGraphs in my app, one for authentication routes, like login and registration and one for the home routes when logged in.
I currently need to pass the navController as a parameter in every composable when I want to use it. That is definitely not scalable and so I'm wondering if it's possible to make it injectable so that I can just request it through a DI framework like Koin. I'm currently using Koin in my App so it would be good if I can just use it's dependency injection tools, to inject a navController as a singleton.

Current Navigation setup

I currently create the navController inside the MainActifity:

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavHostController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        startKoin {
            androidLogger(if (BuildConfig.DEBUG) Level.ERROR else Level.NONE)
            androidContext(this@MainActivity)
            modules(listOf(networkModule, viewModelModule, interactorsModule))
        }

        setContent {
            navController = rememberAnimatedNavController()
            SetupNavGraph(navController = navController)
        }
    }
}

I then pass it into the NavHost that holds the two NavGraphs:

fun SetupNavGraph(
    navController: NavHostController
) {
    AnimatedNavHost(
        navController = navController,
        startDestination = AUTH_GRAPH_ROUTE,
        route = ROOT_GRAPH_ROUTE
    ) {
        homeNavGraph(navController = navController)
        authNavGraph(navController = navController)
    }
}

Below is my NavGraph for all authentication routes. I also pass the NavController as a parameter so that I can use it inside the screen.

fun NavGraphBuilder.authNavGraph(
    navController: NavController
) {
    navigation(
        startDestination = Screen.Login.route,
        route = AUTH_GRAPH_ROUTE
    ) {
        composable(
            Screen.Login.route,
        ) {
            val loginViewModel: LoginViewModel = getViewModel()

            LoginScreen(
                navController = navController,
                state = loginViewModel.state.value,
                onTriggerEvent = loginViewModel::onTriggerEvent
            )
        }
        composable(
            Screen.Register.route,
        ) {
            val registerViewModel: RegisterViewModel = getViewModel()
            RegisterScreen(
                navController = navController,
                state = registerViewModel.state.value,
                onTriggerEvent = registerViewModel::onTriggerEvent
            )
        }
    }
}

The NavGraph for all pages when logged in is similar to my auth one, so not worth showing.

It would be cool if I can just create a navController as a single through Koin but that is not possible because rememberNavController function can only be called inside a Composable.

Is there any solution in solving this problem of passing the navController into every nested composable?

Upvotes: 5

Views: 5894

Answers (1)

Phil Dukhov
Phil Dukhov

Reputation: 87894

For easy testing/previewing, it's recommended to pass only handlers instead of the nav controller itself, and do all real navigation inside SetupNavGraph when a handler is called. More info can be found here.

If it doesn't suits you, you can create your own compositional local, like this:

val LocalNavController = compositionLocalOf<NavHostController> {
    error("No LocalNavController provided")
}

Provide it with CompositionLocalProvider:

setContent {
    CompositionLocalProvider(LocalNavController provides rememberAnimatedNavController()) {
        SetupNavGraph()
    }
}

And then in any composable inside CompositionLocalProvider, you can get your nav controller with LocalNavController.current.

Upvotes: 10

Related Questions