Guia — Jetpack Compose Navigation
If you prefer getting straight into the docs, you can find the documentation on Github and Gitbook here.
When I first started using Compose, I immediately fell in love with it. I wanted my entire application to be written in Compose, even the navigation part. So I started searching for what is the “official” way of navigating with Compose and stumbled upon Compose Navigation.
I was disappointed since it’s basically a port of the Navigation Component which I was already not a fan of. Using Strings to navigate between destinations, so much generated code to have any sort of safe args, etc.
I just wanted something simple, scalable and doesn’t require another library just to have transitions.
Introducing Guia
I wanted a cool catchy name but I suck at naming things so I asked ChatGPT to do it for me, this is the actual conversation (Not really):
Me: Yo ChatGPT, I’m writing an Android Navigation Library for Jetpack Compose. I want some cool unique name, short and easy to remember.
ChatGPT: I gotchu fam, use “Guia”, it means Guidance. Ya welcome. K bai.
Key features:
- Multi Module Support
- Transitions
- Lifecycle, scoped ViewModel support
- Screens, Dialogs, BottomSheets supported from the get go.
- State saving/restoration
- Type safe arguments
- Stateful Type safe results / Also results that can survive process death
- Ability to extend the library to have support for more use cases (Stacks, ViewPager, etc.)
- Deeplinking
- BottomNav navigation with different back handling strategies
Alright, let’s get started.
implementation(“com.roudikk.guia:guia:$currentVersion”)
For the latest version, go here.
Navigation Keys (Destinations)
To declare a destination we can navigate to, we just implement NavigationKey
@Parcelize
class HomeKey: NavigationKey
The key itself is Parcelable
, that’s how we save and restore the state of our backstack. A key can also have arguments, that’s how arguments are passed in Guia:
@Parcelize
data class ProfileKey(val id: String): NavigationKey
The keys don’t have any UI yet, this is on purpose, this makes multi module navigation easier, since we can expose keys to other modules and keep the implementation of it internal, we will see later how we connect keys to an actual UI.
Navigation Nodes
Navigation nodes is how we define a UI for our keys. The three supported types out of the box in Guia are Screen, Dialog and BottomSheet.
Screens don’t necessarily mean a full screen, their dimensions are controlled by their modifiers and the container they’re hosted in.
Dialogs and BottomSheets both have DialogOptions and BottomSheetOptions that can be updated, that means the behaviours of each can be changed in realtime!
For more information and in-depth explanation on navigation nodes, check the NavigationNode part in the Gitbook.
Navigator
Now that we have keys we can navigate to, let’s create a Navigator
. A Navigator
is basically a simple Backstack, in fact the only public function the Navigator
class has is setBackstack(entries: List<BackstackEntry>, isPop: Boolean)
.
To create a Navigator
we start with:
val navigator = rememberNavigator()
From the above, we see that setBackstack
takes in a BackstackEntry
not a NavigationKey
, this is because we need to differentiate a key in our backstack from other keys of similar type, BackstackEntry
is simply an uniquely Id-ed version of a NavigationKey
.
So to set a new Backstack, we can call:
val navigator = rememberNavigator()
navigator.setBackstack(HomeKey().entry(), ProfileKey("some_id").entry())
But, Guia makes it easier to set the backstack with traditional operations and some more advanced ones with extension functions, the long list can be found here.
For example:
Connect a NavigationKey to a NavigationNode
To link a key to a composable, we can do so in two ways. If we really don’t care about separating our keys from our UI, we can use self hosted navigation keys:
If we are in a multi module project and we need to separate the Composables (that would be internal to that module) and keys that need to be accessed from different modules, we can link the two in our rememberNavigator
call, the block initialises a NavigatorConfig
:
Rendering the UI!
Finally, we can render the state of our navigator using NavContainer
:
navigator.NavContainer()
It has some sensible defaults on how screens, bottomsheets and dialogs are rendered but we can override some parameters, check the Gitbook guide for containers for customisation.
Transitions!
Guia uses Compose’s EnterTransition
and ExitTransition
for animation.
The order of deciding which transition to run goes like this:
- Check if the current transition is being overriden
- Check if there’s a key transition, a transition for a specific NavigationKey
- Check if there’s a node transition, a transition for a specific type of NavigationNode
- Finally fall back to a default transition
The above can be set through the NavigatorConfig
First we need to declare a NavigationTransition
, for example:
This will slide the content left/right with some fading in/out as the navigation keys are navigated.
Second, we need to update our NavigatorConfig
to add the transition:
Overriding transitions
We can override a specific transition before we set a backstack, for specific cases where we need to simply override a single setBackstack
's transition.
To do so we can use overrideTransition
:
Results passing
Passing results between navigation keys is done in Guia using the ResultManager
API, it's type safe and stateful.
A Navigator itself is a ResultManager
backed by a stateful key/value map.
Survive state restoration
By simply marking our result as Parcelable
our result can now be saved and restored, the ResultManager
will handle that internally.
@Parcelize
data class Result(val item: String): Parcelable
Passing data between different navigators
The ResultManager
API is public can be used to create our own:
val resultManager = rememberResultManager()
Now we can provide this result manager as CompositionLocal
to children Composables
, that can potentially be hosting different navigators.
And much more…
Guia has more things to offer that we haven’t talked about in this article.
For an indepth documentation, check the Gitbook.
You can also check the code on Github.