Blog Infos
Author
Published
Topics
, , , ,
Published

Generated by gemini.google.com

Finally, Navigation 3 is in production, and I am convinced this will be a game-changer for all Android developers!

I am genuinely excited about these new concepts. If you are just starting your migration or kicking off a new project, you can refer to my previous guide created during the Alpha phase (tip: check the comments there for updates post-release).

But for engineers, the question isn’t just “how do I navigate from A to B?” It is: “How does this handle my complex, production architecture?”

Today, we will focus on two critical concepts that, I had to analyse to migrate of a production app: Deep Links and multiple Scene Strategy. A real-world app obviously doesn’t have just a single navigation flow; it contains multiple concepts. You might have screens that work as a List-Detail pane, while others behave as Bottom Sheets or Dialogs.

Let’s deep dive into these two important concepts to ensure your architecture is ready for the real world.

Manage Deep Links

Deep links are a fundamental component of modern applications. Whether integrating with your web application, connecting to external apps, or navigating from your own notification/widgets, using deep links is a smart and efficient solution.

In our architecture, the Deep Link URI is strictly tied to our navigation, acting as the external entry point that maps directly to a specific screen in the Nav3 graph.

Initialization is straightforward. The first step requires declaring the correct URI structure in your AndroidManifest.xml within the Activity that hosts your navigation. This informs the operating system which URIs your application can handle:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:theme="@style/Theme.MyBooks">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:host="com.stefanoq21.mybooks"
            android:pathPattern=".*"
            android:scheme="https" />
    </intent-filter>
</activity>

With the manifest in place, we can easily trigger a Deep Link using an explicit Intent with specific data. This pattern is essential for creating internal Deep Links (e.g., from notifications or widgets), which we can use to effectively test our Nav3 routing behavior:

Intent(context.applicationContext, MainActivity::class.java)
    .setAction(Intent.ACTION_VIEW)
    .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    .setData("https://com.stefanoq21.mybooks/ReaderEpub/${book!!.bookId}".toUri())

For a structured application, our approach is to capture the incoming Intent.data and, if it is not null, immediately forward it to a dedicated Navigation Manager. In my case, all navigation logic, including Deep Link handling, is managed by a ViewModel.

In the MainActivity, we observe the intent data and dispatch an event to the ViewModel:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
       ...
        setContent {
            MyBooksTheme {
                val navigationViewModel = koinViewModel<NavigationViewModel>()
                LaunchedEffect(intent?.data) {
                    intent?.data?.let {
                        navigationViewModel.onEvent(
                            NavigationEvent.OnDeepLinkArrive(
                                it
                            )
                        )
                    }
                }
             ...

This ensures the ViewModel intercepts the received URI, where we then proceed with parsing and navigation.

Once the URI is received, we parse it to extract the necessary parameters and initiate navigation using the Nav3 routes:

private fun parseUriAndNavigate(uri: Uri) { 
    if (uri.toString().startsWith(URI_TO_READER)) { 
        // We extract the required argument from the URI path.
        backStack.add(Screen.ReaderEpub(uri.lastPathSegment.toString()))
    } 
}

While I use a string prefix check here (URI_TO_READER), the parsing and validation logic can be customized to fit your specific requirements and architectural logic.

With this centralized approach, we can easily manage all the Deep Links your app uses, integrating seamlessly with your new Nav3 screens graph.

Multiple Scene Strategies

Another really important aspect of Navigation 3 is the concept of Scenes. A Scene controls how a screen is presented to the user.

By default, the NavDisplay uses SinglePaneSceneStrategy, which displays content as a standard, full-screen view. However, the power of Scenes lies in their flexibility: you can present screens as Bottom SheetsDialogs, or even within a List-Detail layout (this is possible because a Scene can contain more than one entry at the same time).

So, what are the scene strategies? These allow you to control if a scene can be created. If the strategy allows it, the scene is created otherwise it returns null.

With this concept in mind, we can define specific strategies to control the display of each screen.

At this point, I faced a simple challenge: how do I apply different strategies to different screens?

If we look at the NavDisplay composable, we see it accepts a single parameter for the strategy:

@Composable
public fun <T : Any> NavDisplay(
    backStack: List<T>,
    // ... other parameters
    sceneStrategy: SceneStrategy<T> = SinglePaneSceneStrategy(),
    // ...
    entryProvider: (key: T) -> NavEntry<T>,
)

So, how do we combine them? The answer is both simple and powerful: we can chain strategies using the then function.

val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()
val bottomSheetStrategy = remember { BottomSheetSceneStrategy<NavKey>() }
NavDisplay(
    backStack = navigationViewModel.backStack,
    modifier = modifier,
    // Chain the strategies to return a combined SceneStrategy
    sceneStrategy = listDetailStrategy then bottomSheetStrategy,
    entryProvider = entryProvider {
        // ...

The then function chains the SceneStrategies together. This allows us to simply define entries with different looks by passing the appropriate metadata:

        entry<Screen.Home>(
            metadata = ListDetailScene.listPane()
        ) {
            HomeInitScreen()
        }
        
        entry<Screen.ZoomBook>(
            metadata = ListDetailScene.detailPane()
        ) { key ->
            ZoomBookInitScreen(
                book = key.book,
            )
        }
        entry<Screen.BottomSheetMessage>(
            metadata = BottomSheetSceneStrategy.bottomSheet()
        ) {
            Text("This is a bottom sheet!")
        }

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

Conclusion

It’s clear that Navigation 3 has matured from an exciting experiment into a robust solution for production applications. The architectural shifts it introduces, managing screens as data and giving us direct control over the back stack, fundamentally change how we build Android apps, offering a level of flexibility that was previously hard to achieve.

In my previous article, I mentioned that features like Deep Links were missing pieces of the puzzle. As we’ve seen today, not only are these features are usable, but they also integrate seamlessly with the new paradigm. With these concepts, we can now handle complex, real-world navigation flows with elegant, type-safe code.

While the migration might require a shift in mindset, the payoff is a navigation system that scales with your application’s complexity rather than fighting against it.

To stay updated on the latest patterns and explore more advanced use cases, I highly recommend keeping an eye on the official nav3-recipes project on GitHub. It remains an invaluable resource for the community.

If you found this deep dive helpful, feel free to follow me for more insightful content on Android development and Jetpack Compose. I regularly publish new articles on these topics. Don’t hesitate to share your comments or reach out to me on Bluesky or LinkedIn for further discussions.

Have a great day, and happy coding!

This article was previously published on proandroiddev.com

Menu