In `Nav3` or in compose in general, aren't you ner...
# compose
u
In
Nav3
or in compose in general, aren't you nervous by the non comp time safety, with all these composition locals, and navigating to a unregistered naventry, or not having keys as serializable, all these are runtime crashes, or worse silent non behaviors if decorators are not installed Is this the cost of the flexibility? Is linting then way to make this safer? Has anyone maybe seen lint/detekt rules for Nav3?
k
Composition locals is probably the most misused things. Super easy to set up, and once you get going with a couple of those, it becomes progressively easier to "just" throw in another one. Fast forward a couple of years, you got all these effectively global things that you rely one across so many flows in the app.
u
yea but how do you design around them? Its in the 1st party apis already, nav3 results api, accessing viewmodels etc
im not necessarily against globals, its the runtime crashes of not setup comp locals that scare me (in big codebases)
I can imagine havign a default nav display sith all the mecessary decorator, and lint against raw navdisplay usage what about the unregistered navkey-nav entry, is that feasible?
k
I always try to keep my `CompositionLocal`s
internal
in some kind of low-level module. For example, you can expose a “nav entry factory” function whose lambda will allow you to subscribe to some kind of
Result
and keep the implementation
internal
. Same for the “generic components”. Let’s say you want to have
AsyncImage
composable. This should get
ImageLoader
you can accept it in your root composable, propagate it via
CompositionLocal
and consume in the
AsyncImage
composable. Then, in your app you just use
AsyncImage
without the need to read
ImageLoader
again.
u
whats the default you set in the builder? i.e. if you dont
provides
it, what happens?
k
An exception is being thrown. Since those are defined/provided/used in the low-level module, this must be well tested, so the risk that someone will use it without providing a value in production is very low
u
wdym low level lib? comp locals are usually coming from the top > down
s
They use locals internally and the caller just calls a composable. They don't see the local directly nor do they have access to it. So you just call AsyncImage for example and that internally uses the local
u
Well that's a non issue. I'm talking about the case where you rely on the upstream to fill the composition local Like applying material theme, like providing
image loader
for coil etc If upstream fails to configure, you get a runtime crash. And I'm wondering if this can be caught ahead of time somehow, other than having a 100% coverage of real app integration tests (not feasible)
i.e. from the article, exactly this bit
Copy code
@Composable
fun App(
    modifier: Modifier,
    appState: AppState,
) {
    AppTheme {
        Surface {
            // Root LookaheadScope used to anchor all shared element transitions
            SharedTransitionLayout(
                modifier = modifier.fillMaxSize()
            ) {
                // You may opt to hold a reference to the SharedElementTransitionScope
                appState.sharedElementTransitionScope = this@SharedTransitionLayout
                CompositionLocalProvider(
                    LocalAppState provides appState,
                ) {
                    // The rest of your app's UI goes here, things like the `NavHost`
                    // and so on.
                    ...
                }
            }
        }
    }
}

internal val LocalAppState = staticCompositionLocalOf<AppState> {
    throw IllegalStateException("CompositionLocal LocalAppState not present") <----------
}
s
I mean these things you setup once and never again. Crashing at runtime is fine imo
☝🏻 1
u
Its materially less safe
🤷‍♂️ 1
🤷🏻‍♂️ 1
s
Silently failing will make it more likely you actually ship a version which is faulty
u
? I'm talking about giving up compile time safety in compose
s
Me too 😅