https://github.com/hoc081098/solivagant
π Compose Multiplatform Navigation library - πΈ Pragmatic, type safety navigation for Compose Multiplatform. Based on Freeletics Khonshu Navigation. β₯οΈ ViewModel, SavedStateHandle, Lifecycle, Multi-Backstacks, Transitions, Back-press handling, and more...
https://github.com/hoc081098/solivagant
compose-library compose-multiplatform compose-multiplatform-desktop compose-multiplatform-library compose-multiplatform-navigation compose-navigation jetbrains-compose kmm kmm-library kmm-mvvm kmm-navigation kmp-compose-navigation kmp-library kmp-mvvm kmp-navigation kmp-viewmodel multiplatform-navigation
Last synced: 7 months ago
JSON representation
π Compose Multiplatform Navigation library - πΈ Pragmatic, type safety navigation for Compose Multiplatform. Based on Freeletics Khonshu Navigation. β₯οΈ ViewModel, SavedStateHandle, Lifecycle, Multi-Backstacks, Transitions, Back-press handling, and more...
- Host: GitHub
- URL: https://github.com/hoc081098/solivagant
- Owner: hoc081098
- License: apache-2.0
- Created: 2023-12-28T11:48:05.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2025-04-09T15:37:00.000Z (7 months ago)
- Last Synced: 2025-04-09T16:23:43.500Z (7 months ago)
- Topics: compose-library, compose-multiplatform, compose-multiplatform-desktop, compose-multiplatform-library, compose-multiplatform-navigation, compose-navigation, jetbrains-compose, kmm, kmm-library, kmm-mvvm, kmm-navigation, kmp-compose-navigation, kmp-library, kmp-mvvm, kmp-navigation, kmp-viewmodel, multiplatform-navigation
- Language: Kotlin
- Homepage: https://hoc081098.github.io/solivagant/docs/0.x
- Size: 4.01 MB
- Stars: 99
- Watchers: 2
- Forks: 5
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# solivagant π
## [π’ ACTIVE] π Compose Multiplatform Navigation library - πΈ Pragmatic, type safety navigation for Compose Multiplatform. Based on [Freeletics Khonshu Navigation](https://freeletics.github.io/khonshu/navigation/get-started/). β₯οΈ ViewModel, SavedStateHandle, Lifecycle, Multi-Backstacks, Transitions, Back-press handling, and more...
[](https://search.maven.org/search?q=g:io.github.hoc081098%20solivagant-navigation)
[
](https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/hoc081098/solivagant-navigation)
[](https://codecov.io/gh/hoc081098/solivagant)
[](https://github.com/hoc081098/solivagant/actions/workflows/build.yml)
[](https://github.com/hoc081098/solivagant/actions/workflows/sample.yml)
[](https://github.com/hoc081098/solivagant/actions/workflows/publish-release.yml)
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://github.com/JetBrains/kotlin/releases/tag/v2.0.0)
[](https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.9.0-RC)
[](https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.6.11)
[](https://hits.seeyoufarm.com)
![badge][badge-android]
![badge][badge-jvm]
![badge][badge-js]
![badge][badge-js-ir]
![badge][badge-wasm]
![badge][badge-nodejs]
![badge][badge-linux]
![badge][badge-windows]
![badge][badge-ios]
![badge][badge-mac]
![badge][badge-watchos]
![badge][badge-tvos]
![badge][badge-apple-silicon]
- Integrates with `Jetbrains Compose Multiplatform` seamlessly and easily.
- Integrates with [**kmp-viewmodel**](https://github.com/hoc081098/kmp-viewmodel) library seamlessly and smoothly
- Stack entry scoped `ViewModel`, exists as long as the
stack entry is on the navigation stack, including the configuration changes on `Android`.
- Supports `SavedStateHandle`, used to save and restore data over configuration changes
or process death on `Android`.
- The **navigation stack state** is saved and restored automatically over configuration changes and process
death on `Android`.
On other platforms, you can use a support class provided by this library to store the navigation stack state
as long as you want.
- **Type safety navigation**, easy to pass data between destinations.
No more `String` route and dynamic query parameters.
The `Solivagant` library uses `NavRoute`s and `NavRoot`s to define routes that can be navigated to.
Arguments can be defined as part of the route (a.ka. properties of the route class) and are type safe.
Each `NavRoute` and `NavRoot` has a corresponding `NavDestination` that describes the UI (a.k.a `@Composable`) of the
route.
- Supports **Multi-Backstacks**, this is most commonly used in apps that use bottom navigation to
separate the back stack of each tab.
See [Freeletics Khonshu Navigation - Multiple back stacks](https://freeletics.github.io/khonshu/navigation/back-stacks/)
for more details.
- Supports `LifecycleOwner`, `Lifecycle` events and states, similar to `AndroidX Lifecycle` library.
> [!NOTE]
> This library is still in alpha, so the API may change in the future.
## Credits
- Most of the code in `solivagant-khonshu-navigation-core` and `solivagant-navigation` libraries is
taken from [Freeletics Khonshu Navigation](https://freeletics.github.io/khonshu/navigation/get-started/),
and ported to `Kotlin Multiplatform` and `Compose Multiplatform`.
- The `solivagant-lifecycle` library is inspired
by [Essenty Lifecycle](https://github.com/arkivanov/Essenty?tab=readme-ov-file#lifecycle),
and [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle).
## Author: [Petrus Nguyα»
n ThΓ‘i Hα»c](https://github.com/hoc081098)
Liked some of my work? Buy me a coffee (or more likely a beer)
## Docs & Installation
### **0.x release** docs: https://hoc081098.github.io/solivagant/docs/0.x
### Snapshot docs: https://hoc081098.github.io/solivagant/docs/latest
### Installation
```kotlin
allprojects {
repositories {
[...]
mavenCentral()
}
}
```
```kotlin
implementation("io.github.hoc081098:solivagant-navigation:0.5.0")
```
### Snapshot
Snapshots of the development version are available in Sonatype's snapshots repository.
```kotlin
allprojects {
repositories {
...
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
}
dependencies {
implementation("io.github.hoc081098:solivagant-navigation:0.5.1-SNAPSHOT")
}
```
## Getting started
The library is ported from `Freeletics Khonshu Navigation` library, so the concepts is similar.
You can read the [Freeletics Khonshu Navigation](https://freeletics.github.io/khonshu/navigation/get-started/) to
understand
the concepts.
π Full samples are available [here](#Samples).
### 1. Create `NavRoot`s, `NavRoute`s
```kotlin
@Immutable
@Parcelize
data object StartScreenRoute : NavRoot
@Immutable
@Parcelize
data object SearchProductScreenRoute : NavRoute
```
> [!NOTE]
> `@Parcelize` is provided by `kmp-viewmodel-savedstate` library.
> See [kmp-viewmodel-savedstate](https://github.com/hoc081098/kmp-viewmodel?tab=readme-ov-file#2-kmp-viewmodel-savedstate-library) for more details.
### 2. Create `NavDestination`s along with `Composable`s and `ViewModel`s
###### StartScreen.kt
```kotlin
@JvmField
val StartScreenDestination: NavDestination =
ScreenDestination { StartScreen() }
@Composable
internal fun StartScreen(
modifier: Modifier = Modifier,
// kmpViewModel or kojectKmpViewModel can be used instead.
viewModel: StartViewModel = koinKmpViewModel(),
) {
// UI Composable
}
internal class StartViewModel(
// used to trigger navigation actions from outside the view layer (e.g. from a ViewModel).
// Usually, it is singleton object, or the host Activity retained scope.
private val navigator: NavEventNavigator,
) : ViewModel() {
internal fun navigateToProductsScreen() = navigator.navigateTo(ProductsScreenRoute)
internal fun navigateToSearchProductScreen() = navigator.navigateTo(SearchProductScreenRoute)
}
```
###### SearchProductScreen.kt
```kotlin
@JvmField
val SearchProductScreenDestination: NavDestination =
ScreenDestination { SearchProductsScreen() }
@Composable
internal fun SearchProductsScreen(
modifier: Modifier = Modifier,
// kmpViewModel or kojectKmpViewModel can be used instead.
viewModel: SearchProductsViewModel = koinKmpViewModel(),
) {
// UI Composable
}
internal class SearchProductsViewModel(
private val searchProducts: SearchProducts,
private val savedStateHandle: SavedStateHandle,
// used to trigger navigation actions from outside the view layer (e.g. from a ViewModel).
// Usually, it is singleton object, or the host Activity retained scope.
private val navigator: NavEventNavigator,
) : ViewModel() {
fun navigateToProductDetail(id: Int) {
navigator.navigateTo(ProductDetailScreenRoute(id))
}
}
```
### 3. Setup
#### 3.1. NavHost
Gather all `NavDestination`s in a set and use `NavEventNavigator` to trigger navigation actions.
###### MyAwesomeApp.kt
```kotlin
@Stable
private val AllDestinations: ImmutableSet = persistentSetOf(
StartScreenDestination,
SearchProductScreenDestination,
// and more ...
)
@Composable
fun MyAwesomeApp(
// used to trigger navigation actions from outside the view layer (e.g. from a ViewModel).
// Usually, it is singleton object, or the host Activity retained scope.
navigator: NavEventNavigator = koinInject(),
modifier: Modifier = Modifier,
) {
// BaseRoute is the parent interface of NavRoute and NavRoot.
// It implements Parcelable so that it can be used with rememberSavable.
var currentRoute: BaseRoute? by rememberSavable { mutableStateOf(null) }
NavHost(
modifier = modifier,
// route to the screen that should be shown initially
startRoute = StartScreenRoute,
// should contain all destinations that can be navigated to
destinations = AllDestinations,
// when passing a NavEventNavigator to NavHost, NavHost will take care of setting up the navigator by calling `NavigationSetup(navigator)`
navEventNavigator = navigator,
destinationChangedCallback = { currentRoute = it },
)
}
```
> [!IMPORTANT]
> When passing a `NavEventNavigator` to `NavHost` composable, the NavHost will take care of setting up the navigator by calling `NavigationSetup(navigator)`.
>
> If you don't pass a "global" `NavEventNavigator` to `NavHost` composable, make sure there are property calls to `NavigationSetup(navigator)`.
> For example, we can call `NavigationSetup(navigator)` in each destination composable.
>
> ```kotlin
> @JvmField
> val StartScreenDestination: NavDestination = ScreenDestination {
> NavigationSetup(navigator = koinInject())
> StartScreen()
> }
>
> @JvmField
> val SearchProductScreenDestination: NavDestination = ScreenDestination {
> NavigationSetup(navigator = koinInject())
> SearchProductsScreen()
> }
> ```
>
> π Check out [scoped navigator sample](https://github.com/hoc081098/solivagant/blob/88e75468659fa3185edc47ef6b043ca86aaeee16/samples/simple/shared/src/commonMain/kotlin/com/hoc081098/solivagant/sample/simple/ui/detail/DetailNavigator.kt#L17C7-L17C22) for more information.
#### 3.2. Android
To display `MyAwesomeApp` on `Android`, use `setContent` in `Activity` / `Fragment`.
###### MainActivity.kt
```kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate()
// navigator can be retrieved from the DI container, such as Koin, Dagger Hilt, etc...
setContent {
MyAwesomeApp()
}
}
}
```
#### 3.3. Desktop
To display `MyAwesomeApp` on `Desktop`, use `androidx.compose.ui.window.application` and `Window` composable:
###### main.kt
```kotlin
fun main() {
val lifecycleRegistry = LifecycleRegistry()
val savedStateSupport = SavedStateSupport()
application {
val windowState = rememberWindowState()
val lifecycleOwner = rememberLifecycleOwner(lifecycleRegistry)
LifecycleControllerEffect(
lifecycleRegistry = lifecycleRegistry,
windowState = windowState,
)
savedStateSupport.ClearOnDispose()
Window(
onCloseRequest = ::exitApplication,
title = "Solivagant sample",
state = windowState,
) {
LifecycleOwnerProvider(lifecycleOwner) {
// navigator can be retrieved from the DI container, such as Koin, Koject, etc...
savedStateSupport.ProvideCompositionLocals { MyAwesomeApp() }
}
}
}
}
```
> [!TIP]
> For more information please check out [Desktop sample main.kt](https://github.com/hoc081098/solivagant/blob/2eb1ef4beee875d63aaa882f7198cc738638ad75/samples/sample/desktop/src/commonMain/kotlin/com/hoc081098/solivagant/sample/main.kt#L18-L49)
#### 3.4. iOS / tvOS / watchOS
To display `MyAwesomeApp` on `iOS/tvOS/watchOS`, use `ComposeUIViewController` (Kotlin - iosMain SourceSet) and `UIViewControllerRepresentable` (Swift - native code):
###### MainViewController.kt
```kotlin
val AppLifecycleOwner by lazy { AppLifecycleOwner() }
fun MainViewController(savedStateSupport: SavedStateSupport): UIViewController {
val lifecycleOwnerUIVcDelegate =
LifecycleOwnerComposeUIViewControllerDelegate(hostLifecycleOwner = AppLifecycleOwner)
.apply { bindTo(savedStateSupport) }
.apply { lifecycle.subscribe(LifecycleObserver) }
return ComposeUIViewController(
configure = { delegate = lifecycleOwnerUIVcDelegate },
) {
LifecycleOwnerProvider(lifecycleOwnerUIVcDelegate) {
savedStateSupport.ProvideCompositionLocals { MyAwesomeApp() }
}
}
}
```
###### ComposeView.swift
```swift
private struct ComposeView: UIViewControllerRepresentable {
let savedStateSupport: NavigationSavedStateSupport
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController(savedStateSupport: savedStateSupport)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
}
private class ComposeViewViewModel: ObservableObject {
let savedStateSupport = NavigationSavedStateSupport()
deinit {
self.savedStateSupport.clear()
}
}
struct ComposeViewContainer: View {
@StateObject private var viewModel = ComposeViewViewModel()
var body: some View {
ComposeView(savedStateSupport: viewModel.savedStateSupport)
.ignoresSafeArea(.keyboard) // Compose has own keyboard handler
}
}
```
> [!TIP]
> For more information please check out [iOS sample MainViewController.kt](https://github.com/hoc081098/solivagant/blob/master/samples/sample/shared/src/iosMain/kotlin/com/hoc081098/solivagant/sample/MainViewController.kt)
> and [iosApp sample ComposeView.swift](https://github.com/hoc081098/solivagant/blob/master/samples/sample/iosApp/iosApp/ComposeView.swift)
### 4. Use `NavEventNavigator` in `ViewModel` s / `@Composable` s to trigger navigation actions
```kotlin
// navigate to the destination that the given route leads to
navigator.navigateTo(DetailScreenRoute("some-id"))
// navigate up in the hierarchy
navigator.navigateUp()
// navigate to the previous destination in the backstack
navigator.navigateBack()
// navigate back to the destination belonging to the referenced route and remove all destinations
// in between from the back stack, depending on inclusive the destination
navigator.navigateBackTo(inclusive = false)
```
## Samples
- [Complete sample - Products app](https://github.com/hoc081098/solivagant/tree/master/samples/sample): a complete sample
that demonstrates how to use `solivagant-navigation` in `Compose Multiplatform (Android, Desktop, iOS)`
- `solivagant-navigation` for navigation in Compose Multiplatform.
- `kmp-viewmodel` to share `ViewModel` and `SavedStateHandle`.
- `Koin DI`.
###### Desktop
https://github.com/user-attachments/assets/8b2d806f-3650-47ed-96a2-d9bc72342d25
- [Simple sample - Multibackstacks sample](https://github.com/hoc081098/solivagant/tree/master/samples/simple): a simple sample
that demonstrates how to use `solivagant-navigation` in `Compose Multiplatform (Android, Desktop, iOS)` to
switch between tabs (bottom navigation), but can keep the back stack state of each tab.
Basically, it's a **multi-backstack** demo sample.
###### Desktop
https://github.com/user-attachments/assets/300d7153-ccc0-49b3-a272-ff4f22f66e03
- [Compose Multiplatform KmpViewModel KMM Unsplash Sample](https://github.com/hoc081098/Compose-Multiplatform-KmpViewModel-KMM-Unsplash-Sample): A KMP template of the Unsplash App using Compose multiplatform for Android, Desktop, iOS. Share everything including data, domain, presentation, and UI.
- [Compose Multiplatform Todo solivagant Sample](https://github.com/hoc081098/Compose-Multiplatform-Todo-solivagant-Sample): A KMP template of the Todo App using Compose multiplatform for Android, Desktop, iOS and Web. Share everything including data, domain, presentation, and UI.
- [Solivagant Wasm Sample](https://github.com/hoc081098/solivagant/tree/master/samples/solivagant-wasm-sample).
- [Kotlin Multiplatform app template with shared UI, using solivagant-navigation](https://github.com/hoc081098/KMP-App-Template-solivagant).
## Roadmap
- [ ] Add more tests
- [x] Add more samples
- [ ] Add docs
- [x] Review supported targets
- [ ] Polish and improve the implementation and the public API
- [x] Support transition when navigating (since [0.1.0](https://github.com/hoc081098/solivagant/releases/tag/0.1.0)).
- [x] Support more targets such as wasm, watchOS, tvOS, etc... (since [0.2.0](https://github.com/hoc081098/solivagant/releases/tag/0.2.0)).
## π’ Active status
This library is actively maintained and updated with new features and bug fixes.
## License
```license
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
```
[badge-android]: http://img.shields.io/badge/-android-6EDB8D.svg?style=flat
[badge-android-native]: http://img.shields.io/badge/support-[AndroidNative]-6EDB8D.svg?style=flat
[badge-wearos]: http://img.shields.io/badge/-wearos-8ECDA0.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/-jvm-DB413D.svg?style=flat
[badge-js]: http://img.shields.io/badge/-js-F8DB5D.svg?style=flat
[badge-js-ir]: https://img.shields.io/badge/support-[IR]-AAC4E0.svg?style=flat
[badge-nodejs]: https://img.shields.io/badge/-nodejs-68a063.svg?style=flat
[badge-linux]: http://img.shields.io/badge/-linux-2D3F6C.svg?style=flat
[badge-windows]: http://img.shields.io/badge/-windows-4D76CD.svg?style=flat
[badge-wasm]: https://img.shields.io/badge/-wasm-624FE8.svg?style=flat
[badge-apple-silicon]: http://img.shields.io/badge/support-[AppleSilicon]-43BBFF.svg?style=flat
[badge-ios]: http://img.shields.io/badge/-ios-CDCDCD.svg?style=flat
[badge-mac]: http://img.shields.io/badge/-macos-111111.svg?style=flat
[badge-watchos]: http://img.shields.io/badge/-watchos-C0C0C0.svg?style=flat
[badge-tvos]: http://img.shields.io/badge/-tvos-808080.svg?style=flat
