navigation
navigation copied to clipboard
Kotlin multi-platform application navigation library.

navigation
Kotlin multi-platform application navigation library that supports Jetpack Compose.
val navigator = rememberNavigator(initialDestination = "Greeting")
NavigationContainer(navigator) { (destination, _) ->
when (destination) {
"Greeting" -> Column {
Text("Hello")
Button(onClick = { navigator.push("Farewell") }) {
Text("Say Goodbye")
}
}
"Farewell" -> Text("Good-bye")
else -> Text("Unexpected Destination: $destination")
}
}
Getting Started 🏁
The library is provided through Repsy.io. Checkout
the releases page to get the latest version.
Repository
repositories {
maven {
url = uri("https://repo.repsy.io/mvn/chrynan/public")
}
}
Dependencies
core
implementation("com.chrynan.navigation:navigation-core:VERSION")
compose
implementation("com.chrynan.navigation:navigation-compose:VERSION")
Usage 👨💻
Destinations
NavigationDestinations represent the locations within an application that a Navigator can coordinate. They can be of
any type and can contain any data that is useful to render the destination UI. A common approach is to use enums or
sealed classes to represent the different NavigationDestinations within an application.
enum class AppDestination {
HOME,
SEARCH,
SETTINGS,
DETAILS
}
Contexts
A NavigationContext is a way of representing complex or nested navigation destinations. A NavigationContext defines
its initialDestination and has its own stack of NavigationDestinations associated with it within the internal
implementation of a Navigator. This allows there to be a stack of stacks of NavigationDestinations for an
application.
enum class MainContext(
override val initialDestination: AppDestination,
val title: String,
val icon: ImageVector
) : NavigationContext<AppDestination> {
HOME(title = "Home", icon = Icons.Default.Home, initialDestination = AppDestination.Home),
SEARCH(title = "Search", icon = Icons.Default.Search, initialDestination = AppDestination.Search),
SETTINGS(title = "Settings", icon = Icons.Default.Settings, initialDestination = AppDestination.Settings)
}
Navigator
A Navigator is used to navigate between navigation contexts and destinations via the
convenient push, popContext, and popDestination functions. A Navigator can be obtained via one of the
constructor functions or the remember/rememberSavable functions when using Jetpack Compose/Multiplatform Compose.
val navigator = rememberNavigator(initialDestination = AppDestination.HOME)
BackHandler { navigator.popDestination() }
ListItem(modifier = Modifier.clickable { navigator.push(AppDestination.DETAILS) })
NavigationContainer
The NavigationContainer composable provides a convenient way to listening to destination and context state changes and
recomposing its content accordingly. Just provide a Navigator instance and a content composable.
@Composable
fun App() {
val navigator = rememberNavigator(initialDestination = AppDestination.HOME)
NavigationContainer(navigator = navigator) { (destination, context) ->
when (destination) {
AppDestination.HOME -> HomeScreenComposable()
AppDestination.SEARCH -> SearchScreenComposable()
AppDestination.SETTINGS -> SettingsScreenComposable()
AppDestination.DETAILS -> DetailsScreenComposable()
}
}
}
Transitions and animations
You have complete control over the composable functions that render the UI of the application and can use the Jetpack
Compose library's transition and animation APIs to change between the navigation context and destination UIs. For more
fine-grained control, create a custom composable replacing the NavigationContainer that handles transitions properly
for your application. Then just listen and react to the Navigator.state changes.
@Composable
fun <Destination : NavigationDestination, Context : NavigationContext<Destination>> MyNavContainer(
navigator: Navigator<Destination, Context>,
) {
val context = navigator.store.context.collectAsState()
val destination = navigator.store.destination.collectAsState()
// Render UI from context and destination values and apply any transition or animation desired.
}
Best Practices
- Avoid passing a
Navigatorto@Composablefunctions and instead hoist the state.
@Composable
fun App() {
val navigator = rememberNavigator(...)
...
MyScreen(
onBackPressed = { navigator.popDestination() },
onGoToDetails = { navigator.push(AppDestination.Details(it)) }
)
}
- Use different
Navigatorsfor deep nested navigation. This way each component can retain its own navigation hierarchy and delegate to its encapsulating component via state hoisting if it cannot handle the navigation.
@Composable
fun ParentComponent() {
val parentNavigator = rememberNavigator(...)
...
ChildComponent(onBack = { parentNavigator.popDestination() })
}
@Composable
fun ChildComponent(
onBack: () -> Unit
) {
val childNavigator = rememberNavigator(...)
BackHandler {
if (!childNavigator.canPopDestination()) {
onBack.invoke()
}
}
}
- Use the
rememberSavableNavigatorfunction along with serializable navigation destinations and contexts to retain the navigation state between configuration changes.
val navigator = rememberSavableNavigator(
initialContext = MainContext.HOME,
destinationSerializer = AppDestination.serializer(),
contextSerializer = MainContext.serializer()
)
- Create the
Navigatorinstance outside of@Composablefunctions to handle navigation outside the user interface flow, such as in Activity lifecycle callbacks.
val navigator = Navigator(initialContext = MainContext.HOME)
- Utilize side-effects in Jetpack Compose for handling navigation to non-composable UI components, such as starting new Activities or changing Fragments.
NavigationContainer(navigator) { (destination, _) ->
when (destination) {
is AppDestination.Details -> LaunchedEffect(destination) {
context.startActivity(DetailsActivity.newIntent(context, destinatin.id))
}
}
}
- Utilize kotlinx serialization and
the serialization-parcelable library to transfer a
Navigatorbetween components.
val navigatorSerializer = Navigator.serializer(destinationSerializer, contextSerializer)
parcelable.encodeToParcel(serializer = navigatorSerializer, value = navigator)
json.encodeToString(serializer = navigatorSerializer, value = navigator)
Documentation 📃
More detailed documentation is available in the docs folder. The entry point to the documentation can be found here.
Security 🛡️
For security vulnerabilities, concerns, or issues, please responsibly disclose the information either by opening a public GitHub Issue or reaching out to the project owner.
Contributing ✍️
Outside contributions are welcome for this project. Please follow the code of conduct and coding conventions when contributing. If contributing code, please add thorough documents. and tests. Thank you!
Sponsorship ❤️
Support this project by becoming a sponsor of my work! And make sure to give the repository a ⭐
License ⚖️
Copyright 2023 chRyNaN
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.