Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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: 15 days 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 (11 months ago)
- Default Branch: master
- Last Pushed: 2024-05-28T22:57:07.000Z (6 months ago)
- Last Synced: 2024-05-29T03:42:54.661Z (6 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: 2.62 MB
- Stars: 33
- Watchers: 1
- Forks: 3
- Open Issues: 10
-
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...
[![maven-central](https://img.shields.io/maven-central/v/io.github.hoc081098/solivagant-navigation)](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)
[![codecov](https://codecov.io/gh/hoc081098/solivagant/branch/master/graph/badge.svg?token=jBFg12osvP)](https://codecov.io/gh/hoc081098/solivagant)
[![Build and publish snapshot](https://github.com/hoc081098/solivagant/actions/workflows/build.yml/badge.svg)](https://github.com/hoc081098/solivagant/actions/workflows/build.yml)
[![Build sample](https://github.com/hoc081098/solivagant/actions/workflows/sample.yml/badge.svg)](https://github.com/hoc081098/solivagant/actions/workflows/sample.yml)
[![Publish Release](https://github.com/hoc081098/solivagant/actions/workflows/publish-release.yml/badge.svg)](https://github.com/hoc081098/solivagant/actions/workflows/publish-release.yml)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
[![Kotlin version](https://img.shields.io/badge/Kotlin-2.0.0-blueviolet?logo=kotlin&logoColor=white)](https://github.com/JetBrains/kotlin/releases/tag/v2.0.0)
[![KotlinX Coroutines version](https://img.shields.io/badge/Kotlinx_Coroutines-1.9.0_RC-blueviolet?logo=kotlin&logoColor=white)](https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.9.0-RC)
[![Compose Multiplatform version](https://img.shields.io/badge/Compose_Multiplatform-1.6.11-blueviolet?logo=kotlin&logoColor=white)](https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.6.11)
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fhoc081098%2Fsolivagant&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](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: NavigationSavedStateSupportfunc 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](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`.- [Simple 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.- [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