Navigation 3 in Compose Multiplatform
Android's Navigation library has been upgraded to Navigation 3, introducing a redesigned approach to navigation that works with Compose and takes into account feedback to the previous version of the library. Starting with version 1.10, Compose Multiplatform supports adopting Navigation 3 in multiplatform projects for all supported platforms: Android, iOS, desktop, and web.
Key changes
Navigation 3 is more than a new version of the library — in a lot of ways it's a new library entirely. To learn more about the philosophy behind this redesign, see the Android Developers blog post.
Key changes in Navigation 3 include:
User-owned back stack. Instead of operating a single library back stack, you create and manage a
SnapshotStateListof states, which the UI observes directly.Low-level building blocks. Thanks to closer integration with Compose, the library allows more flexibility in implementing your own navigation components and behavior.
Adaptive layout system. With adaptive design, you can display multiple destinations at the same time and seamlessly switch between layouts.
Learn more about general design of Navigation 3 in Android documentation.
Dependencies setup
To try out the multiplatform implementation of Navigation 3, add the following dependency to your version catalog:
For projects using the Material 3 Adaptive and ViewModel libraries, also add the following navigation support artifacts:
Finally, you can try out the proof-of-concept library created by a JetBrains engineer. The library integrates the multiplatform Navigation 3 with browser history navigation on the web:
Browser history navigation is expected to be supported by the base multiplatform Navigation 3 library in version 1.1.0.
Multiplatform support
Navigation 3 is closely aligned with Compose, allowing an Android navigation implementation to work in shared Compose Multiplatform code with minimal changes. To support non-JVM platforms like web and iOS, you need to implement polymorphic serialization for destination keys.
You can compare extensive examples of Android-only and multiplatform apps using Navigation 3 on GitHub:
Polymorphic serialization for destination keys
On Android, Navigation 3 relies on reflection-based serialization, which is not available when you target non-JVM platforms like iOS. To address this limitation, the library has two overloads for the rememberNavBackStack() function:
The first overload only takes a set of
NavKeyreferences and requires a reflection-based serializer.The second overload also takes a
SavedStateConfigurationparameter that allows you to provide aSerializersModuleand handle open polymorphism across all platforms.
The Navigation 3 multiplatform examples define routes and register them with a SavedStateConfiguration, as shown below:
Recommended serialization approaches
When implementing multiplatform navigation, you'll need to choose how to organize and serialize your route definitions. Depending on your project's complexity and modularization, use one of the following three patterns.
Single module with sealed type
For small projects where all routes exist in one module, use a sealed interface. This is the most straightforward approach as Kotlin serialization handles the hierarchy automatically:
Alternatively, if you want to use the rememberNavBackStack() function explicitly, here's a slightly different configuration:
Multi-module with aggregated sealed types
For more complex projects with routes defined in multiple modules, you can define a sealed type for each module. Then, aggregate their serializers in the app module using the subclassesOfSealed() function.
With Dependency Injection (DI), you can also use DI containers to collect serializers for sealed types from each module into a Set<KSerializer> dynamically.
Multi-module with individual route registration
If your routes cannot be grouped into sealed types, you can manually combine SerializersModule instances from different modules.
This approach offers a high level of flexibility and decoupling, though it requires more manual maintenance. Similar to the multi-module with aggregated sealed types approach, you can use DI to assemble the list of serializers dynamically, which can improve flexibility.
What's next
Navigation 3 is covered in-depth on the Android Developer portal. While some of the documentation uses Android-specific examples, the core concepts and navigation principles remain consistent across all platforms:
Overview of Navigation 3 with advice on managing state, modularizing navigation code, and animation.
Migration from Navigation 2 to Navigation 3. It's easier to see Navigation 3 as a new library than a new version of the existing library, so it's less of a migration and more of a rewrite. But the guide points out the general steps to take.