Jetpack Compose navigation made declarative
- Download
- Defining destinations
- Navigation host
- Navigation
- Navigation arguments
- Return result
- Nested graphs
- Deep links
- Android Studio not indexing generated files
repositories {
...
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.LanarsInc.compose-easy-route:compose-easy-route:{version}")
implementation("com.github.LanarsInc.compose-easy-route:compose-easy-route-core:{version}")
ksp("com.github.LanarsInc.compose-easy-route:compose-easy-route-generator:{version}")
}
To mark a Composable as a destination, use the @Destination
annotation:
@Destination(name = "foo-page")
@Composable
fun FooPage() {
/* */
}
@Destination
annotation takes one mandatory parameter:
name
- name of the destination route
EasyRouteNavHost
listens to navigation commands and triggers NavController
. It uses Compose Navigation's NavHost
under the hood.
EasyRouteNavHost
has three mandatory parameters:
navigationManager
- used for sending navigation commands toEasyRouteNavHost
navGraph
- generatedNavigationGraph
object fromNavGraphs
objectstartDestination
- initial destination
val navigationManager = rememberNavigationManager()
EasyRouteNavHost(
navigationManager = navigationManager,
navGraph = NavGraphs.root,
startDestination = FirstPageDestination
)
NOTE: Destination can only be used as start if it doesn't have parameters, or has only optional parameters.
ComposeEasyRoute navigation API is similar to original Compose Navigation's.
navigationManager.navigate(FooPageDestination())
Navigating with arguments:
navigationManager.navigate(BarPageDestination(id = 42, name = "Name"))
navigationManager.popBackStack()
Pop up to specific destination:
navigationManager.popBackStack(FooPageDestination)
navigationManager.navigate(FooPageDestination()) {
popUpTo(BarPageDestination) { inclusive = true }
}
Pop the whole back stack
navigationManager.navigate(FooPageDestination()) {
popUntilRoot { inclusive = true }
}
To declare navigation arguments you can simply add them to the Composable function:
@Destination(name = "foo-page")
@Composable
fun FooPage(
id: Int? = null, // <- this will be an optional navigation argument
name: String // <- this will be a mandatory navigation argument
)
Int
Float
Long
Boolean
String
Serializable
Parcelable
Enums
IntArray
FloatArray
LongArray
BooleanArray
Array<String>
Array<Serializable>
Array<Parcelable>
NOTE: ComposeEasyRoute supports passing Serializable
and Parcelable
objects, however this approach is not recommended by Google. The official documentation says:
Passing complex data structures over arguments is considered an anti-pattern. Each destination should be responsible for loading UI data based on the minimum necessary information, such as item IDs. This simplifies process recreation and avoids potential data inconsistencies.
So you should avoid it as much as you can.
To return the result to the previous screen use popBackStack
with withResult
function.
navigationManager.popBackStack {
withResult(
key = "key",
value = value
)
}
On the screen to where you return the result, observe the result in this way:
LaunchedEffect(true) {
navigationManager.currentBackStackEntry?.collectResult<YourResultType?>(
key = "key",
initialValue = null
) { result ->
// process the result
}
}
You can return the result not only to the previous screen, but to any destinnation in back stack.
navigationManager.popBackStack(
destination = FooPageDestination,
inclusive = false
) {
withResult(
key = "key",
value = value
)
}
By default, all your destinations will belong to root NavigationGraph
. This NavigationGraph
instance will be generated in an object called NavGraphs
. So, you can access it via NavGraphs.root
and you should pass it into EasyRouteNavHost
call.
To define a nested graph, you need to create an annotation class annotated with @NavGraph
. For example:
@NavGraph(route = "registration")
annotation class RegistrationNavGraph(
val start: Boolean = false
)
Note that start
parameter is mandatory. It is used to mark destinations as start. Exactly one destination within specific navigation graph must be marked as start
.
The @NavGraph
annotation takes one mandatory parameter:
route
- the route of the navigation graph
To make destinations part of this navigation graph, you need to annotate them with it:
@RegistrationNavGraph(start = true)
@Destination(name = "foo-page")
@Composable
fun FooPage() {
/* */
}
By default, all nested graphs are children of the root navigation graph. To define a nested graph as a child of another nested graph, use parent
parameter of @NavGraph
annotatioon:
@NavGraph(
route = "confirmation",
parent = RegistrationNavGraph::class
)
annotation class ConfirmationNavGraph
Sometimes you want to create another NavHost that is independent from main NavHost (e.g. when implementing bottom navigation or bottom sheet). For that you will need to define a navigation graph, and mark it as independent
, so ComposeEasyRoute will know that this navigation graph is separate from main navigation graph. Also, for independent graphs we don't need the start
parameter, because startDestination
will be passed right into EasyRouteNavHost
.
NOTE: Independent graphs can't be defined as children of other graphs.
@NavGraph(
route = "bottom-navigation",
independent = true
)
annotation class BottomNavigationGraph
@BottomNavigationNavGraph
@Destination("books")
@Composable
fun BooksScreen() {
/* */
}
val navigationManager = remember { NavigationManager() }
EasyRouteNavHost(
navigationManager = navigationManager,
navGraph = NavGraphs.bottomNavigation,
startDestination = BooksScreenDestination
)
Sometimes its useful to scope ViewModel to a specific navigation graph. ComposeEasyRoute provides a way to do that by obtaining parent backstack entry in Composable
through parameter of type NavBackStackEntry
, annotated with @ParentBackStackEntry
, and passing it to viewModel()
function.
@RegistrationNavGraph
@Destination(name = "foo-page")
@Composable
fun FooPage(
@ParentBackStackEntry parentBackStackEntry: NavBackStackEntry
) {
val viewModel = viewModel<SharedViewModel>(parentBackStackEntry)
// if using Hilt
val hiltViewModel = hiltViewModel<SharedViewModel>(parentBackStackEntry)
/* */
}
You can define deep links to a destination like this:
@Destination(
name = "foo-page",
deepLinks = [
EasyRouteDeepLink(uriPattern = "https://www.example.com/foo/{id}")
]
)
@Composable
fun FooPage(id: Int) {
/* */
}
EasyRouteDeepLink
also has action
and mimeType
parameters. Read more about deep linking in official documentation.
See KSP related issue.
Fix: put this code inside android
block.
applicationVariants.all {
kotlin.sourceSets {
getByName(name) {
kotlin.srcDir("build/generated/ksp/$name/kotlin")
}
}
}