How to acess NavHostController from nested composable Jetpack Compose
Asked Answered
M

2

10

Assume that my application looks like this

@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController, startDestination = Route.Home.route) {
        /// other composes
        composable("Home") { HomeCompose(navController) }
    }
}

@Composable
fun HomeCompose(navController: NavHostController) {
    ChildCompose(navController)
}

@Composable
fun ChildCompose(navController: NavHostController) {
    navController.navigate("")
}

I want to access navController in nested composable to navigate but I dont want to pass navController from parent composable to child compsable as above

Is there anyway to access navController from anywhere inside NavHost without passing it through composable hierarchy

Edit: for now, I can use CompositionLocalProvider to access navController in nested compose as below

val AppNavController = compositionLocalOf<NavHostController>() { error("NavHostController error") }

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    CompositionLocalProvider(
        AppNavController provides navController
    ) {
        NavHost(navController, startDestination = Route.Home.route) {
            /// other composes
            composable("Home") { HomeCompose() }
        }
    }
}

@Composable
fun HomeCompose() {
    ChildCompose()
}

@Composable
fun ChildCompose(navController: NavHostController) {
    val navController = AppNavController.current
    Column(modifier = Modifier.clickable {
        navController.navigate("Content")
    }) {
        ...
    }
}
Mauser answered 12/4, 2021 at 14:1 Comment(6)
Did you read the Testing Navigation Compose guide which specifically recommends not doing this?Connacht
@Connacht I don't think passing data through compose hierarchy is good approach, suppose that I have too many nested compose that need to use navControllerMauser
in case there's no elegant way I have to use this approach for nowMauser
@Mauser Did you read the link? Your ChildCompose composable should work independently from Navigation, then just use a lambda function.Futch
@GabrieleMariotti yes, I just read itMauser
Nice trick, but unfortunely not useable in multimodule projectsRecommendatory
F
7

With compose 1.0.0-beta04 and navigation 1.0.0-alpha10 as suggested by the official doc

  • Pass lambdas that should be triggered by the composable to navigate, rather than the NavController itself.
    @Composable
    fun ChildCompose(
        navigateTo: () -> Unit
    ) {
        //...
        navigateTo
    }

and to use it:

ChildCompose(navigateTo = {
    navController.navigate("...")
})

In this way ChildCompose composable works independently from Navigation

Futch answered 12/4, 2021 at 15:31 Comment(2)
what if we have many nested component and wanna use navigate on last child? we should pass it frequently?Longbow
I would recommend passing it around. But if it becomes too tedious then consider using a CompositionLocal.Claude
P
0

The navHostController can be safely stored in the Application class. Because navigation is global and has a lifecycle that lives for then entirety of the app, and because navHostController is not a composable, storing it in the Application class is perfectly fine:

class App : Application() {
    lateinit var navController: NavHostController

    override fun onCreate() {
        super.onCreate()
        context = this
    }

    companion object {
        lateinit var context: App
    }
}

Make sure to and the name of your application class to the AndroidManifest.xml file:

<manifest>
    <application
        android:name=".App"
        ...
    </application>
</manifest>

You then need to set the variable navController in your activity as shown here:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            App.context.navController = rememberNavController()

            NavDemoTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { _ ->
                    NavigationManager()
                }
            }
        }
    }
}

You can then access your navHostController in any composable, although it is recommended that you create a hoisted Composable for screens and place the code for accessing the navHostController there. Here is an example:

@Composable
fun HomeScreenHandler() {
    val vm = HomeScreenViewModel()

    HomeScreen(
        onGotoSettings = {
            App.context.navController.navigate(route = SETTINGS_SCREEN)
        }
    )

}

@Composable
fun HomeScreen(
    onGotoSettings: () -> Unit
) {
    Surface {
        Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
            Text(text = "This is the home screen")
            Button(onClick = {
                onGotoSettings()
            }) {
                Text("Go to Settings screen")
            }
        }
    }
}

Having worked with Compose since the beta version, I was disappointed in many of the ways things were done in Compose. Navigation was the biggest issue. As a result, I developed a framework that replaces the navigation framework of Compose with a much better one. But best of all, the framework properly handles developing responsive apps and sticks to the concept of "Separation of Concerns" by delegating the selecting of composables to the framework rather than having to write code directly in composables to deal with system configuration changes like orientation change, device resolution, night mode, etc. It's available on Github. Just search for Jetmagic. Try it out. You'll love the way it handles navigation.

Precinct answered 31/8, 2024 at 16:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.