Coder Social home page Coder Social logo

hoc081098 / solivagant Goto Github PK

View Code? Open in Web Editor NEW
52.0 3.0 3.0 2.7 MB

🔆 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...

Home Page: https://hoc081098.github.io/solivagant/docs/0.x

License: Apache License 2.0

Kotlin 99.86% Shell 0.14%
compose-multiplatform compose-multiplatform-library compose-multiplatform-navigation compose-library compose-multiplatform-desktop compose-navigation jetbrains-compose kmm kmm-library kmm-mvvm

solivagant's Introduction

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...

maven-central codecov Build and publish snapshot Build sample Publish Release GitHub license Kotlin version KotlinX Coroutines version Compose Multiplatform version Hits

badge badge badge badge badge badge badge badge badge badge badge badge badge

  • Integrates with Jetbrains Compose Multiplatform seamlessly and easily.

  • Integrates with 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 NavRoutes and NavRoots 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 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

Liked some of my work? Buy me a coffee (or more likely a beer)

Buy Me A Coffee

Docs

Installation

allprojects {
  repositories {
    [...]
    mavenCentral()
  }
}
implementation("io.github.hoc081098:solivagant-navigation:0.3.0")

Getting started

The library is ported from Freeletics Khonshu Navigation library, so the concepts is similar. You can read the Freeletics Khonshu Navigation to understand the concepts.

👉 Full samples are available here.

1. Create NavRoots, NavRoutes

@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 for more details.

2. Create NavDestinations along with Composables and ViewModels

StartScreen.kt
@JvmField
val StartScreenDestination: NavDestination =
  ScreenDestination<StartScreenRoute> { 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
@JvmField
val SearchProductScreenDestination: NavDestination =
  ScreenDestination<SearchProductScreenRoute> { SearchProductsScreen() }

@Composable
internal fun SearchProductsScreen(
  modifier: Modifier = Modifier,
  // kmpViewModel or kojectKmpViewModel can be used instead.
  viewModel: SearchProductsViewModel = koinKmpViewModel<SearchProductsViewModel>(),
) {
  // 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 NavDestinations in a set and use NavEventNavigator to trigger navigation actions.

MyAwesomeApp.kt
@Stable
private val AllDestinations: ImmutableSet<NavDestination> = 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.

@JvmField
val StartScreenDestination: NavDestination = ScreenDestination<StartScreenRoute> {
  NavigationSetup(navigator = koinInject())
  StartScreen()
}

@JvmField
val SearchProductScreenDestination: NavDestination = ScreenDestination<SearchProductScreenRoute> {
  NavigationSetup(navigator = koinInject())
  SearchProductsScreen()
}

👉 Check out scoped navigator sample for more information.

3.2. Android

To display MyAwesomeApp on Android, use setContent in Activity / Fragment.

MainActivity.kt
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
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

3.4. iOs / tvOS / watchOS

To display MyAwesomeApp on iOS/tvOS/watchOS, use ComposeUIViewController (Kotlin - iosMain SourceSet) and UIViewControllerRepresentable (Swift - native code):

MainViewController.kt
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
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 and iosApp sample ComposeView.swift

4. Use NavEventNavigator in ViewModel s / @Composable s to trigger navigation actions

// 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<MainScreenRoute>(inclusive = false)

Samples

Roadmap

  • Add more tests
  • Add more samples
  • Add docs
  • Review supported targets
  • Polish and improve the implementation and the public API
  • Support transition when navigating (since 0.1.0).
  • Support more targets such as wasm, watchOS, tvOS, etc... (since 0.2.0).

License

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

solivagant's People

Contributors

dependabot[bot] avatar hoc081098 avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

solivagant's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Repository problems

These problems occurred while renovating this repository. View logs.

  • WARN: Use matchDepPatterns instead of matchPackagePatterns

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/build.yml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3
  • actions/cache v4
  • codecov/codecov-action v4.5.0
  • actions/upload-artifact v4
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3
  • actions/cache v4
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3
  • actions/cache v4
  • actions/setup-python v5
  • JamesIves/github-pages-deploy-action v4.6.1
.github/workflows/publish-release.yml
  • actions/checkout v4
  • ffurrer2/extract-release-notes v2
  • softprops/action-gh-release v2
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3
  • actions/cache v4
  • actions/setup-python v5
  • JamesIves/github-pages-deploy-action v4.6.1
.github/workflows/sample.yml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/actions v3
  • actions/cache v4
  • actions/checkout v4
  • actions/setup-java v4
  • maxim-lobanov/setup-xcode v1
  • gradle/actions v3
  • actions/cache v4
  • sersoft-gmbh/xcodebuild-action v3
gradle
gradle.properties
settings.gradle.kts
  • org.gradle.toolchains.foojay-resolver-convention 0.8.0
build.gradle.kts
gradle/libs.versions.toml
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-android 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-swing 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-test 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 1.8.1
  • junit:junit 4.13.2
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.3
  • org.jetbrains.kotlinx:kotlinx-collections-immutable 0.3.7
  • io.github.aakira:napier 2.7.1
  • io.github.hoc081098:FlowExt 0.8.1
  • io.insert-koin:koin-core 3.5.6
  • io.insert-koin:koin-compose 1.1.5
  • io.insert-koin:koin-android 3.5.6
  • io.insert-koin:koin-androidx-compose 3.5.6
  • io.nlopez.compose.rules:detekt 0.4.4
  • androidx.lifecycle:lifecycle-runtime-ktx 2.8.2
  • androidx.annotation:annotation 1.8.0
  • androidx.activity:activity-compose 1.9.0
  • io.coil-kt.coil3:coil-core 3.0.0-alpha06
  • io.coil-kt.coil3:coil-compose 3.0.0-alpha06
  • io.coil-kt.coil3:coil-network-ktor 3.0.0-alpha06
  • io.ktor:ktor-client-core 2.3.12
  • io.ktor:ktor-client-okhttp 2.3.12
  • io.ktor:ktor-client-java 2.3.12
  • io.ktor:ktor-client-darwin 2.3.12
  • io.github.hoc081098:kmp-viewmodel 0.7.1
  • io.github.hoc081098:kmp-viewmodel-savedstate 0.7.1
  • io.github.hoc081098:kmp-viewmodel-compose 0.7.1
  • io.github.hoc081098:kmp-viewmodel-koin 0.7.1
  • io.github.hoc081098:kmp-viewmodel-koin-compose 0.7.1
  • io.github.hoc081098:solivagant-navigation 0.3.0
  • com.benasher44:uuid 0.8.4
  • org.jetbrains.kotlin.multiplatform 1.9.24
  • org.jetbrains.kotlin.android 1.9.24
  • org.jetbrains.kotlin.native.cocoapods 1.9.24
  • org.jetbrains.kotlin.plugin.serialization 1.9.24
  • org.jetbrains.kotlin.plugin.parcelize 1.9.24
  • org.jetbrains.compose 1.6.11
  • com.android.application 8.5.0
  • com.android.library 8.5.0
  • com.diffplug.gradle.spotless 6.25.0
  • io.gitlab.arturbosch.detekt 1.23.6
  • org.jetbrains.kotlinx.kover 0.7.6
  • org.jetbrains.dokka 1.9.20
  • org.jetbrains.kotlinx.binary-compatibility-validator 0.14.0
  • dev.drewhamilton.poko 0.15.3
  • com.vanniktech.maven.publish 0.29.0
khonshu-navigation-core/gradle.properties
khonshu-navigation-core/build.gradle.kts
lifecycle/gradle.properties
lifecycle/build.gradle.kts
navigation/gradle.properties
navigation/build.gradle.kts
  • io.kotest:kotest-assertions-core 5.10.0.1466-SNAPSHOT
samples/sample/androidApp/build.gradle.kts
samples/sample/desktop/build.gradle.kts
samples/sample/shared/build.gradle.kts
samples/simple/androidApp/build.gradle.kts
  • com.squareup.leakcanary:leakcanary-android 2.14
samples/simple/desktop/build.gradle.kts
samples/simple/shared/build.gradle.kts
samples/solivagant-wasm-sample/build.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.8
pip_requirements
docs/requirements.txt
  • mkdocs ==1.6.0
  • mkdocs-material ==9.5.27
  • pymdown-extensions ==10.8.1
  • mdx_truly_sane_lists ==1.3

  • Check this box to trigger a request for Renovate to run again on this repository

Prepare for 0.0.1

  • Readme badges, GHA
  • Changelog
  • Remove obsolete docs
  • Workflow + secrets
  • License in Kotlin files
  • Supported targets

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.