Hey everyone, I'm working on a large Android app i...
# compose-android
s
Hey everyone, I'm working on a large Android app in Jetpack Compose, and we've designed its navigation with a deep, tree-based hierarchy. To manage complexity and modularize, we've opted for a structure where certain "nodes" or feature areas in this tree actually contain their own
NavHost
with an independent
NavController
. For example, our main
NavHost
might show
RootScreenA
, which then composes a
FeatureANavHost
(with
featureANavController
) internally. Similarly,
RootScreenB
might compose
FeatureBNavHost
(with
featureBNavController
). The challenge we're facing is how to achieve coordination or navigation between these separate
NavController
instances. For instance, if an action occurs deep within `FeatureANavHost`'s stack, we might need to navigate to a specific screen within `FeatureBNavHost`'s stack. Currently, we're exploring methods like using a global event bus/coordinator (e.g.,
SharedFlow
or a simple object with callbacks accessible at a higher level) to "signal" navigation events from one independent `NavController`'s domain to another. The coordinator then explicitly calls
navigate()
on the target
NavController
. While this works, it feels like we're working against the core principles of Jetpack Compose Navigation, which typically advocates for a single
NavController
for the entire app, leveraging nested
navigation { ... }
blocks. My questions are: 1. Is this pattern of having multiple independent `NavHost`es (each with its own
NavController
) considered an anti-pattern for a single-activity app, even for modularity in a deep tree structure? 2. If it is, what are the recommended patterns or best practices to achieve similar modularity and hierarchical organization (especially with deep nesting like 10+ levels) while sticking to a single
NavController
? 3. If this multi-
NavController
approach can be viable under specific circumstances, what are the most robust ways to handle cross-controller "navigation" or coordination, and what are the major pitfalls to watch out for beyond basic isolation? Any insights, examples, or pointers to relevant architectural discussions would be greatly appreciated! Thanks in advance!
d
I am working with a similar architecture with different NavControllers and NavHosts which are feature specific. Right now, we have made something call NavigationCallbacks interface with <T> and we have made our own extension function using this NavigationCallbacks and NavGraphBuilder. This callback is accessible by NavController and we are using it to send data back to our NavGraphBuilder where we can share it to a different NavGraph. Personally, I have not tried using SharedFlow to make something like an event bus for the same but that would work too I guess.
s
But How efficient is this approach with the deeplinks? Looks like there has to be a manual effort from the architecture POV.
d
Not very efficient to be honest, we had time constraints and we knew the downside to this would be passing the interface or individual callbacks where ever we would need to access it and also whenever we needed some new callback for data. As for deeplinks they will work fine, have not faced any issues regarding this. I have not revisited the same yet but I do plan to have a common navigation data manager that will not hold data but help pass it back and forth, and also clear it depending on lifecycle. From an architecture pov, we are using MVI and have an inline function setup for handling data via callbacks so implementation was not complex but tedious. If you have any approach in mind, do share, would love to brain storm regarding this.
s
Okay, but if there are 3 screens A B C hosted on the RootScreen all three of them have their own nav controller. And lets say there is D screen which is a child of C. Then how D is opened via deeplink? As D is having its own graph which is completly unaware of the the RootScreen or RootNavController?
Since I am working on kmp and cmp. I am quite constrained on the libraries to use, hence taking an inspiration from the uber RIB architecture. But I am stuck on the deeplink part. As in rib the builder is responsible for creating dependencies, I am forced to call build function inside the composavle which is not correct Ideally it should be invoked from the router.
Okay, but if there are 3 screens A B C hosted on the RootScreen all three of them have their own nav controller. And lets say there is D screen which is a child of C. Then how D is opened via deeplink? As D is having its own graph which is completly unaware of the the RootScreen or RootNavController?
@Debayan can you help me on this