https://kotlinlang.org logo
#compose
Title
# compose
m

Mihai Voicescu

12/08/2021, 5:06 PM
I have a
NavHostController
to navigate using the
ComposeNavigator
API. I need access to it in certain
Compose
functions. Is using the
CompositionLocal
API suitable for efficiently pass it around? Seems like they need access to the same reference and I couldn't find a function that already does that... Something like this
Copy code
val navController = rememberNavController()

    CompositionLocalProvider(LocalNavHostController provides navController) {
        NavHost(navController = navController, startDestination = navController.pathToMainScreen) {
            composable(navController.pathToMainScreen) { MainScreen() }
            composable(navController.pathToWifiScreen) { WiFiScreen() }
        }
    }
m

mattinger

12/08/2021, 5:49 PM
keep in mind things in a composition local can’t be referenced in non composable code, such as on click listeners. As @Adam Powell said, it’s better to pass it as a parameter. That way you can reference it directly in your non composable code, rather than having to create an artificial variable in composable code just to use it in a listener.
That said, i did take the opposite approach for our logging mechanism, and used CompositionLocal because i didn’t want it littering our function signatures all the way down the tree.
i

Ian Lake

12/08/2021, 5:51 PM
We have specific guidance against having references to NavController leak into each of your screens, either directly or indirectly: https://developer.android.com/jetpack/compose/navigation#testing
1
m

mattinger

12/08/2021, 5:53 PM
to be honest, for navcontroller, i tend to pass down lambdas for navigation beyond the top level:
Copy code
fun MyScreen(...., onNavigateToSomeScreen: () -> Unit)
which matches that article i guess
so i’m doing it right 😉
👍 1
I have a standards doc i’m writing with some of the things i see people doing in a less than optimal way, and pointing the best practices. This is one of the things i have documented there.
m

Mihai Voicescu

12/08/2021, 6:13 PM
The problem is passing a lambda in 6+ levels of Composable fun is not exactly nice. When dealing with state, I generally separate the Composable functions similarly to how Flutter does: 1 that holds the state(State) and another pure one(View). The pure one (View) can be easily tested and previewed. Using the same strategy I can make the View accept a lambda as explained in https://developer.android.com/jetpack/compose/navigation#testing so it is decoupled from the NavController. As for the State it will procure the lambda using the
CompositionLocal
as to not "having references to NavController leak into each of your screens". This approach seems to avoid all the pitfalls and have a good development experience. What do you guys think? @mattinger @Ian Lake
2
m

mattinger

12/08/2021, 6:16 PM
It will work, but you have to consider how you are going to test things like this. Your composable can no longer be passed a lambda function. You’d have to surround your function call with a CompositionLocalProvider to set the value, which is a bit awkward in my opinion
👍 1
a

Adam Powell

12/08/2021, 8:35 PM
if you're passing things through 6+ layers of composable then you should have a look at these patterns: https://kiranrao.in/blog/2021/12/03/jetpack-compose-slots/ and https://chris.banes.dev/slotting-in-with-compose-ui/
👍 1
m

Mihai Voicescu

12/09/2021, 9:16 AM
@Adam Powell I meant the NavController is created 6+ levels of composable up, in the first Composable (called by the MainActivity) so it didn't make sense to pass down that much. Thanks for the articles though. I have been using the same design but didn't know how it was called 😁.
2 Views