├── .github
├── FUNDING.yml
├── ci-gradle.properties
└── workflows
│ ├── mkdocs-requirements.txt
│ ├── build.yaml
│ ├── docs-publish.yaml
│ └── release.yaml
├── voyager-koin
├── api
│ ├── android
│ │ └── voyager-koin.api
│ └── desktop
│ │ └── voyager-koin.api
├── gradle.properties
└── build.gradle.kts
├── samples
├── multi-module
│ ├── app
│ │ ├── .gitignore
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ └── strings.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ └── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ │ ├── java
│ │ │ │ └── cafe
│ │ │ │ │ └── adriel
│ │ │ │ │ └── voyager
│ │ │ │ │ └── sample
│ │ │ │ │ └── multimodule
│ │ │ │ │ ├── SampleApp.kt
│ │ │ │ │ └── SampleActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle.kts
│ ├── navigation
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── java
│ │ │ │ └── cafe
│ │ │ │ └── adriel
│ │ │ │ └── voyager
│ │ │ │ └── sample
│ │ │ │ └── multimodule
│ │ │ │ └── navigation
│ │ │ │ └── SharedScreen.kt
│ │ └── build.gradle.kts
│ ├── feature-posts
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── java
│ │ │ │ └── cafe
│ │ │ │ └── adriel
│ │ │ │ └── voyager
│ │ │ │ └── sample
│ │ │ │ └── multimodule
│ │ │ │ └── posts
│ │ │ │ ├── ScreenModule.kt
│ │ │ │ ├── ListScreen.kt
│ │ │ │ └── DetailsScreen.kt
│ │ └── build.gradle.kts
│ └── feature-home
│ │ └── build.gradle.kts
├── multiplatform-iosApp
│ ├── Configuration
│ │ └── Config.xcconfig
│ ├── iosApp
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── app-icon-1024.png
│ │ │ │ └── Contents.json
│ │ │ └── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── iOSApp.swift
│ │ ├── ContentView.swift
│ │ └── Info.plist
│ └── iosApp.xcodeproj
│ │ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcuserdata
│ │ │ └── gabriel.lopes.xcuserdatad
│ │ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ └── gabriel.lopes.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── multiplatform
│ ├── src
│ │ ├── androidMain
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ └── strings.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.webp
│ │ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ └── drawable-v24
│ │ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── kotlin
│ │ │ │ └── cafe
│ │ │ │ │ └── adriel
│ │ │ │ │ └── voyager
│ │ │ │ │ └── sample
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── SampleActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── jsMain
│ │ │ ├── resources
│ │ │ │ ├── style.css
│ │ │ │ └── index.html
│ │ │ └── kotlin
│ │ │ │ └── main.js.kt
│ │ ├── iosMain
│ │ │ └── kotlin
│ │ │ │ └── MainViewController.kt
│ │ ├── wasmJsMain
│ │ │ ├── kotlin
│ │ │ │ └── main.wasmJs.kt
│ │ │ └── resources
│ │ │ │ └── index.html
│ │ ├── desktopMain
│ │ │ └── kotlin
│ │ │ │ └── cafe
│ │ │ │ └── adriel
│ │ │ │ └── voyager
│ │ │ │ └── sample
│ │ │ │ └── multiplatform
│ │ │ │ └── App.kt
│ │ ├── macosMain
│ │ │ └── kotlin
│ │ │ │ └── main.macos.kt
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── sample
│ │ │ └── multiplatform
│ │ │ └── Application.kt
│ └── README.md
└── android
│ ├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ └── strings.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ └── activity_legacy.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── sample
│ │ │ ├── koinIntegration
│ │ │ ├── KoinScreenModel.kt
│ │ │ ├── KoinIntegrationActivity.kt
│ │ │ └── KoinScreen.kt
│ │ │ ├── kodeinIntegration
│ │ │ ├── KodeinScreenModel.kt
│ │ │ ├── KodeinScopedDependencySample.kt
│ │ │ ├── KodeinIntegrationActivity.kt
│ │ │ └── KodeinScreen.kt
│ │ │ ├── androidViewModel
│ │ │ ├── AndroidDetailsViewModel.kt
│ │ │ ├── AndroidViewModelActivity.kt
│ │ │ ├── AndroidListViewModel.kt
│ │ │ ├── AndroidListScreen.kt
│ │ │ └── AndroidDetailsScreen.kt
│ │ │ ├── hiltIntegration
│ │ │ ├── HiltListScreenModel.kt
│ │ │ ├── HiltMainActivity.kt
│ │ │ ├── HiltDetailsViewModel.kt
│ │ │ ├── HiltDetailsScreenModel.kt
│ │ │ ├── HiltListViewModel.kt
│ │ │ ├── HiltListScreen.kt
│ │ │ ├── HiltModule.kt
│ │ │ └── HiltDetailsScreen.kt
│ │ │ ├── screenModel
│ │ │ ├── ListScreenModel.kt
│ │ │ ├── ScreenModelActivity.kt
│ │ │ ├── DetailsScreenModel.kt
│ │ │ ├── ListScreen.kt
│ │ │ └── DetailsScreen.kt
│ │ │ ├── androidLegacy
│ │ │ ├── LegacyOneScreenModel.kt
│ │ │ ├── LegacyTwoScreenModel.kt
│ │ │ ├── LegacyActivity.kt
│ │ │ └── LegacyModule.kt
│ │ │ ├── rxjavaIntegration
│ │ │ ├── RxJavaIntegrationActivity.kt
│ │ │ ├── RxJavaScreenModel.kt
│ │ │ └── RxJavaScreen.kt
│ │ │ ├── liveDataIntegration
│ │ │ ├── LiveDataIntegrationActivity.kt
│ │ │ ├── LiveDataScreenModel.kt
│ │ │ └── LiveDataScreen.kt
│ │ │ ├── bottomSheetNavigation
│ │ │ ├── BottomSheetNavigationActivity.kt
│ │ │ └── BackScreen.kt
│ │ │ ├── basicNavigation
│ │ │ └── BasicNavigationActivity.kt
│ │ │ ├── parcelableScreen
│ │ │ └── ParcelableActivity.kt
│ │ │ ├── tabNavigation
│ │ │ └── tabs
│ │ │ │ ├── HomeTab.kt
│ │ │ │ ├── ProfileTab.kt
│ │ │ │ └── FavoritesTab.kt
│ │ │ └── App.kt
│ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── voyager-core
├── gradle.properties
├── consumer-rules.pro
├── src
│ ├── commonWebMain
│ │ └── kotlin
│ │ │ └── cafe.adriel.voyager.core
│ │ │ ├── lifecycle
│ │ │ ├── Serializable.native.kt
│ │ │ └── ConfigurationChecker.kt
│ │ │ ├── platform
│ │ │ └── KClassEx.js.kt
│ │ │ ├── screen
│ │ │ ├── ScreenKey.web.kt
│ │ │ └── Screen.native.kt
│ │ │ └── concurrent
│ │ │ ├── AtomicInt32.js.kt
│ │ │ ├── PlatformDispatcher.js.kt
│ │ │ ├── ThreadSafeMap.js.kt
│ │ │ └── ThreadSafeSet.js.kt
│ ├── nativeMain
│ │ └── kotlin
│ │ │ └── cafe.adriel.voyager.core
│ │ │ ├── lifecycle
│ │ │ ├── Serializable.native.kt
│ │ │ └── ConfigurationChecker.kt
│ │ │ ├── screen
│ │ │ ├── ScreenKey.native.kt
│ │ │ └── Screen.native.kt
│ │ │ ├── platform
│ │ │ └── KClassEx.native.kt
│ │ │ └── concurrent
│ │ │ ├── AtomicInt32.native.kt
│ │ │ ├── PlatformDispatcher.native.kt
│ │ │ ├── ThreadSafeSet.native.kt
│ │ │ ├── ThreadSafeMutableIterator.kt
│ │ │ └── ThreadSafeMutableCollection.kt
│ ├── commonJvmMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── core
│ │ │ ├── concurrent
│ │ │ ├── AtomicInt32.kt
│ │ │ ├── ThreadSafeSet.kt
│ │ │ ├── ThreadSafeList.kt
│ │ │ └── ThreadSafeMap.kt
│ │ │ ├── lifecycle
│ │ │ └── Serializable.jvm.kt
│ │ │ ├── screen
│ │ │ ├── ScreenKey.jvm.kt
│ │ │ └── Screen.kt
│ │ │ └── platform
│ │ │ └── KClassEx.jvm.kt
│ ├── commonMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── core
│ │ │ ├── platform
│ │ │ └── KClassEx.kt
│ │ │ ├── concurrent
│ │ │ ├── AtomicInt32.kt
│ │ │ ├── PlatformDispatcher.kt
│ │ │ ├── ThreadSafeMap.kt
│ │ │ ├── ThreadSafeSet.kt
│ │ │ └── ThreadSafeList.kt
│ │ │ ├── registry
│ │ │ ├── ScreenModule.kt
│ │ │ ├── ScreenProvider.kt
│ │ │ └── ScreenRegistry.kt
│ │ │ ├── screen
│ │ │ ├── ScreenKey.kt
│ │ │ └── Screen.kt
│ │ │ ├── lifecycle
│ │ │ ├── ConfigurationChecker.kt
│ │ │ ├── Serializable.kt
│ │ │ ├── NavigatorScreenLifecycle.kt
│ │ │ ├── ScreenLifecycleOwner.kt
│ │ │ └── LifecycleEffectStore.kt
│ │ │ ├── internal
│ │ │ └── SafeCollections.kt
│ │ │ ├── annotation
│ │ │ └── InternalVoyagerApi.kt
│ │ │ └── stack
│ │ │ └── Stack.kt
│ ├── commonJvmTest
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── core
│ │ │ └── utils
│ │ │ └── Quadruple.kt
│ ├── androidMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── core
│ │ │ ├── concurrent
│ │ │ └── PlatformDispatcher.android.kt
│ │ │ └── lifecycle
│ │ │ └── ConfigurationChecker.kt
│ └── desktopMain
│ │ └── kotlin
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── core
│ │ ├── concurrent
│ │ └── PlatformDispatcher.desktop.kt
│ │ └── lifecycle
│ │ └── ConfigurationChecker.kt
└── build.gradle.kts
├── voyager-hilt
├── gradle.properties
├── src
│ └── main
│ │ └── java
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── hilt
│ │ ├── ScreenModelFactory.kt
│ │ ├── ScreenModelFactoryKey.kt
│ │ ├── ScreenModelKey.kt
│ │ ├── internal
│ │ └── ContextExt.kt
│ │ ├── OptionalMultibindingsModule.kt
│ │ ├── ScreenModelEntryPoint.kt
│ │ └── VoyagerHiltViewModelFactories.kt
└── build.gradle.kts
├── voyager-kodein
├── gradle.properties
├── build.gradle.kts
├── api
│ ├── android
│ │ └── voyager-kodein.api
│ └── desktop
│ │ └── voyager-kodein.api
└── src
│ └── commonMain
│ └── kotlin
│ └── cafe
│ └── adriel
│ └── voyager
│ └── kodein
│ └── ScreenModel.kt
├── voyager-rxjava
├── gradle.properties
├── build.gradle.kts
├── api
│ ├── android
│ │ └── voyager-rxjava.api
│ └── desktop
│ │ └── voyager-rxjava.api
└── src
│ └── commonJvmMain
│ └── kotlin
│ └── cafe
│ └── adriel
│ └── voyager
│ └── rxjava
│ └── ScreenModel.kt
├── voyager-livedata
├── gradle.properties
├── api
│ └── voyager-livedata.api
├── src
│ └── main
│ │ └── java
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── livedata
│ │ └── LiveScreenModel.kt
└── build.gradle.kts
├── voyager-navigator
├── gradle.properties
├── src
│ ├── commonMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ ├── lifecycle
│ │ │ ├── NavigatorDisposable.kt
│ │ │ └── NavigatorLifecycleStore.kt
│ │ │ └── internal
│ │ │ ├── NavigatorBackHandler.kt
│ │ │ ├── LifecycleProvider.kt
│ │ │ └── NavigatorSaverInternal.kt
│ ├── desktopMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── internal
│ │ │ └── Actuals.kt
│ ├── iosMain
│ │ └── kotlin
│ │ │ └── cafe.adriel.voyager.navigator.internal
│ │ │ └── Actuals.uikit.kt
│ ├── macosMain
│ │ └── kotlin
│ │ │ └── cafe.adriel.voyager.navigator.internal
│ │ │ └── Actuals.macos.kt
│ ├── commonWebMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── internal
│ │ │ └── Actuals.web.kt
│ ├── androidMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ ├── internal
│ │ │ ├── Actuals.kt
│ │ │ └── LifecycleProvider.android.kt
│ │ │ └── NavigatorSaver.android.kt
│ └── nonAndroidMain
│ │ └── kotlin
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── navigator
│ │ └── internal
│ │ └── LifecycleProvider.nonAndroid.kt
└── build.gradle.kts
├── docs
├── media
│ ├── icon
│ │ └── logo.png
│ └── assets
│ │ ├── fade.gif
│ │ ├── scale.gif
│ │ ├── slide.gif
│ │ ├── stack.gif
│ │ ├── tab-nav.gif
│ │ ├── basic-nav.gif
│ │ ├── nested-nav.gif
│ │ ├── Sem título-1.png
│ │ ├── ezgif.com-gif-maker.gif
│ │ ├── navigation-android.gif
│ │ ├── ezgif.com-gif-maker (1).gif
│ │ └── navigation-bottom-sheet.gif
├── back-press.md
├── deep-links.md
├── android-viewmodel
│ ├── index.md
│ ├── hilt-integration.md
│ └── viewmodel-kmp.md
├── screenmodel
│ ├── kodein-integration.md
│ ├── koin-integration.md
│ ├── livedata-integration.md
│ └── hilt-integration.md
├── navigation
│ └── nested-navigation.md
└── faq.md
├── voyager-screenmodel
├── gradle.properties
├── build.gradle.kts
├── src
│ └── commonMain
│ │ └── kotlin
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── core
│ │ └── model
│ │ ├── NavigatorScreenModel.kt
│ │ └── ScreenModel.kt
└── api
│ ├── android
│ └── voyager-screenmodel.api
│ └── desktop
│ └── voyager-screenmodel.api
├── voyager-transitions
├── gradle.properties
├── build.gradle.kts
└── src
│ └── commonMain
│ └── kotlin
│ └── cafe
│ └── adriel
│ └── voyager
│ └── transitions
│ └── CrossfadeTransition.kt
├── voyager-lifecycle-kmp
├── gradle.properties
├── api
│ ├── android
│ │ └── voyager-lifecycle-kmp.api
│ └── desktop
│ │ └── voyager-lifecycle-kmp.api
├── src
│ ├── nonAndroidMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── jetpack
│ │ │ └── AndroidScreenLifecycleOwner.nonAndroid.kt
│ ├── commonMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── jetpack
│ │ │ ├── NavigatorLifecycleKMPOwner.kt
│ │ │ ├── ScreenLifecycleJetpackOwner.kt
│ │ │ ├── LifecycleProvider.kt
│ │ │ └── navigator.kt
│ └── androidMain
│ │ └── kotlin
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── jetpack
│ │ └── AndroidScreenLifecycleOwner.android.kt
└── build.gradle.kts
├── voyager-tab-navigator
├── gradle.properties
├── build.gradle.kts
└── src
│ └── commonMain
│ └── kotlin
│ └── cafe
│ └── adriel
│ └── voyager
│ └── navigator
│ └── tab
│ └── Tab.kt
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── samples.versions.toml
├── voyager-bottom-sheet-navigator
├── gradle.properties
├── src
│ ├── desktopMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── bottomSheet
│ │ │ └── internal
│ │ │ └── Actuals.kt
│ ├── commonWebMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── bottomSheet
│ │ │ └── internal
│ │ │ └── Actuals.web.kt
│ ├── macosMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── bottomSheet
│ │ │ └── internal
│ │ │ └── Actuals.macos.kt
│ ├── iosMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── bottomSheet
│ │ │ └── internal
│ │ │ └── Actuals.uikit.kt
│ ├── androidMain
│ │ └── kotlin
│ │ │ └── cafe
│ │ │ └── adriel
│ │ │ └── voyager
│ │ │ └── navigator
│ │ │ └── bottomSheet
│ │ │ └── internal
│ │ │ └── Actuals.kt
│ └── commonMain
│ │ └── kotlin
│ │ └── cafe
│ │ └── adriel
│ │ └── voyager
│ │ └── navigator
│ │ └── bottomSheet
│ │ └── internal
│ │ └── BottomSheetNavigatorBackHandler.kt
└── build.gradle.kts
├── .editorconfig
├── .gitattributes
├── includes.gradle.kts
├── gradle.properties
├── LICENSE.md
├── .gitignore
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: adrielcafe
--------------------------------------------------------------------------------
/voyager-koin/api/android/voyager-koin.api:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/voyager-koin/api/desktop/voyager-koin.api:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/multi-module/app/.gitignore:
--------------------------------------------------------------------------------
1 | debug/
2 | release/
3 |
--------------------------------------------------------------------------------
/voyager-core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerCore
2 | POM_ARTIFACT_ID=voyager-core
--------------------------------------------------------------------------------
/voyager-hilt/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerHilt
2 | POM_ARTIFACT_ID=voyager-hilt
--------------------------------------------------------------------------------
/voyager-koin/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerKoin
2 | POM_ARTIFACT_ID=voyager-koin
--------------------------------------------------------------------------------
/voyager-kodein/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerKodein
2 | POM_ARTIFACT_ID=voyager-kodein
--------------------------------------------------------------------------------
/voyager-rxjava/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerRxJava
2 | POM_ARTIFACT_ID=voyager-rxjava
--------------------------------------------------------------------------------
/voyager-livedata/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerLiveData
2 | POM_ARTIFACT_ID=voyager-livedata
--------------------------------------------------------------------------------
/voyager-navigator/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerNavigator
2 | POM_ARTIFACT_ID=voyager-navigator
--------------------------------------------------------------------------------
/docs/media/icon/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/icon/logo.png
--------------------------------------------------------------------------------
/docs/media/assets/fade.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/fade.gif
--------------------------------------------------------------------------------
/docs/media/assets/scale.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/scale.gif
--------------------------------------------------------------------------------
/docs/media/assets/slide.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/slide.gif
--------------------------------------------------------------------------------
/docs/media/assets/stack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/stack.gif
--------------------------------------------------------------------------------
/voyager-screenmodel/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerScreenModel
2 | POM_ARTIFACT_ID=voyager-screenmodel
--------------------------------------------------------------------------------
/voyager-transitions/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerTransitions
2 | POM_ARTIFACT_ID=voyager-transitions
--------------------------------------------------------------------------------
/docs/media/assets/tab-nav.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/tab-nav.gif
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerLifecycleKMP
2 | POM_ARTIFACT_ID=voyager-lifecycle-kmp
--------------------------------------------------------------------------------
/voyager-tab-navigator/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerTabNavigator
2 | POM_ARTIFACT_ID=voyager-tab-navigator
--------------------------------------------------------------------------------
/docs/media/assets/basic-nav.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/basic-nav.gif
--------------------------------------------------------------------------------
/docs/media/assets/nested-nav.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/nested-nav.gif
--------------------------------------------------------------------------------
/docs/media/assets/Sem título-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/Sem título-1.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/docs/media/assets/ezgif.com-gif-maker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/ezgif.com-gif-maker.gif
--------------------------------------------------------------------------------
/docs/media/assets/navigation-android.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/navigation-android.gif
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=VoyagerBottomSheetNavigator
2 | POM_ARTIFACT_ID=voyager-bottom-sheet-navigator
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=org.example.project.KotlinProject
3 | APP_NAME=KotlinProject
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Voyager
3 |
4 |
--------------------------------------------------------------------------------
/voyager-core/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | -keepclassmembers class * implements cafe.adriel.voyager.core.screen.Screen {
2 | public getKey();
3 | }
--------------------------------------------------------------------------------
/docs/media/assets/ezgif.com-gif-maker (1).gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/ezgif.com-gif-maker (1).gif
--------------------------------------------------------------------------------
/docs/media/assets/navigation-bottom-sheet.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/docs/media/assets/navigation-bottom-sheet.gif
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Voyager MultiModule
3 |
4 |
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{java,kt,kts,xml,kt.spec,kts.spec}]
4 | charset = utf-8
5 | insert_final_newline = true
6 | max_line_length = 120
7 | end_of_line = crlf
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/jsMain/resources/style.css:
--------------------------------------------------------------------------------
1 | #root {
2 | width: 100%;
3 | height: 100vh;
4 | }
5 |
6 | #root > .compose-web-column > div {
7 | position: relative;
8 | }
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/lifecycle/Serializable.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | public actual interface JavaSerializable
4 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/lifecycle/Serializable.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | public actual interface JavaSerializable
4 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct iOSApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ContentView()
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/samples/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Voyager
3 | This is a legacy content
4 |
5 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multi-module/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/concurrent/AtomicInt32.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | public actual typealias AtomicInt32 = java.util.concurrent.atomic.AtomicInteger
4 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/platform/KClassEx.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.platform
2 |
3 | import kotlin.reflect.KClass
4 |
5 | public expect val KClass<*>.multiplatformName: String?
6 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/lifecycle/Serializable.jvm.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import java.io.Serializable
4 |
5 | public actual interface JavaSerializable : Serializable
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/screen/ScreenKey.jvm.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import java.util.UUID
4 |
5 | internal actual fun randomUuid(): String = UUID.randomUUID().toString()
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/AtomicInt32.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | public expect class AtomicInt32(initialValue: Int) {
4 | public fun getAndIncrement(): Int
5 | }
6 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/screen/ScreenKey.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import platform.Foundation.NSUUID
4 |
5 | internal actual fun randomUuid(): String = NSUUID().UUIDString()
6 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/platform/KClassEx.js.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.platform
2 |
3 | import kotlin.reflect.KClass
4 |
5 | public actual val KClass<*>.multiplatformName: String? get() = simpleName
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/platform/KClassEx.jvm.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.platform
2 |
3 | import kotlin.reflect.KClass
4 |
5 | public actual val KClass<*>.multiplatformName: String? get() = qualifiedName
6 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/platform/KClassEx.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.platform
2 |
3 | import kotlin.reflect.KClass
4 |
5 | public actual val KClass<*>.multiplatformName: String? get() = qualifiedName
6 |
--------------------------------------------------------------------------------
/.github/ci-gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.daemon=false
2 |
3 | org.gradle.parallel=true
4 | org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError
5 | #org.gradle.workers.max=2
6 |
7 | kotlin.compiler.execution.strategy=in-process
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/samples/multiplatform/src/iosMain/kotlin/MainViewController.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication
3 |
4 | fun MainViewController() = ComposeUIViewController { SampleApplication() }
5 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeSet.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import java.util.concurrent.CopyOnWriteArraySet
4 |
5 | public actual class ThreadSafeSet : MutableSet by CopyOnWriteArraySet()
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeList.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import java.util.concurrent.CopyOnWriteArrayList
4 |
5 | public actual class ThreadSafeList : MutableList by CopyOnWriteArrayList()
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeMap.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 | public actual class ThreadSafeMap : MutableMap by ConcurrentHashMap()
6 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmTest/kotlin/cafe/adriel/voyager/core/utils/Quadruple.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.utils
2 |
3 | internal data class Quadruple(
4 | val first: A,
5 | val second: B,
6 | val third: C,
7 | val fourth: D
8 | )
9 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenModule.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.registry
2 |
3 | private typealias ScreenModule = ScreenRegistry.() -> Unit
4 |
5 | public fun screenModule(block: ScreenModule): ScreenModule =
6 | { block() }
7 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelFactory.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | /**
4 | * A custom factory used to instantiate objects with custom constructor.
5 | * Frequently used with Assisted Injection
6 | */
7 | public interface ScreenModelFactory
8 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/screen/ScreenKey.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | public typealias ScreenKey = String
4 |
5 | internal expect fun randomUuid(): String
6 |
7 | public val Screen.uniqueScreenKey: ScreenKey
8 | get() = "Screen#${randomUuid()}"
9 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/lifecycle/NavigatorDisposable.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.lifecycle
2 |
3 | import cafe.adriel.voyager.navigator.Navigator
4 |
5 | public interface NavigatorDisposable {
6 | public fun onDispose(navigator: Navigator)
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gabriel.lopes.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrielcafe/voyager/HEAD/samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gabriel.lopes.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/screen/ScreenKey.web.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import kotlin.uuid.ExperimentalUuidApi
4 | import kotlin.uuid.Uuid
5 |
6 | @OptIn(ExperimentalUuidApi::class)
7 | internal actual fun randomUuid(): String = Uuid.random().toString()
8 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/desktopMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit
7 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/commonWebMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.web.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit
7 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/macosMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.macos.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit
7 |
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.koinIntegration
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.sample.sampleItems
5 |
6 | class KoinScreenModel : ScreenModel {
7 |
8 | val items = sampleItems
9 | }
10 |
--------------------------------------------------------------------------------
/samples/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
4 | import kotlinx.coroutines.CoroutineDispatcher
5 |
6 | @InternalVoyagerApi
7 | public expect val PlatformMainDispatcher: CoroutineDispatcher
8 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.kodeinIntegration
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.sample.sampleItems
5 |
6 | class KodeinScreenModel : ScreenModel {
7 |
8 | val items = sampleItems
9 | }
10 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/iosMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.uikit.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | // TODO: use ios backstack
6 | @Composable
7 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) = Unit
8 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/screen/Screen.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | public actual interface Screen {
6 | public actual val key: ScreenKey
7 | get() = commonKeyGeneration()
8 |
9 | @Composable
10 | public actual fun Content()
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/screen/Screen.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | public actual interface Screen {
6 | public actual val key: ScreenKey
7 | get() = commonKeyGeneration()
8 |
9 | @Composable
10 | public actual fun Content()
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-livedata/api/voyager-livedata.api:
--------------------------------------------------------------------------------
1 | public abstract class cafe/adriel/voyager/livedata/LiveScreenModel : cafe/adriel/voyager/core/model/ScreenModel {
2 | public fun (Ljava/lang/Object;)V
3 | protected final fun getMutableState ()Landroidx/lifecycle/MutableLiveData;
4 | public final fun getState ()Landroidx/lifecycle/LiveData;
5 | public fun onDispose ()V
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/voyager-navigator/src/desktopMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 |
6 | @InternalVoyagerApi
7 | @Composable
8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit
9 |
--------------------------------------------------------------------------------
/voyager-navigator/src/iosMain/kotlin/cafe.adriel.voyager.navigator.internal/Actuals.uikit.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 |
6 | @InternalVoyagerApi
7 | @Composable
8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit
9 |
--------------------------------------------------------------------------------
/voyager-navigator/src/macosMain/kotlin/cafe.adriel.voyager.navigator.internal/Actuals.macos.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 |
6 | @InternalVoyagerApi
7 | @Composable
8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit
9 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/wasmJsMain/kotlin/main.wasmJs.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.ExperimentalComposeUiApi
2 | import androidx.compose.ui.window.CanvasBasedWindow
3 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication
4 |
5 | @OptIn(ExperimentalComposeUiApi::class)
6 | fun main() {
7 | CanvasBasedWindow(canvasElementId = "ComposeTarget") { SampleApplication() }
8 | }
9 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonWebMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.web.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 |
6 | @InternalVoyagerApi
7 | @Composable
8 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = Unit
9 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidViewModel
2 |
3 | import androidx.lifecycle.ViewModel
4 |
5 | class AndroidDetailsViewModel(
6 | val index: Int
7 | ) : ViewModel() {
8 |
9 | override fun onCleared() {
10 | println("ViewModel: clear details")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/multi-module/navigation/src/main/java/cafe/adriel/voyager/sample/multimodule/navigation/SharedScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule.navigation
2 |
3 | import cafe.adriel.voyager.core.registry.ScreenProvider
4 |
5 | sealed class SharedScreen : ScreenProvider {
6 | object PostList : SharedScreen()
7 | data class PostDetails(val id: String) : SharedScreen()
8 | }
9 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/Actuals.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | internal actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) =
8 | BackHandler(enabled, onBack)
9 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.sample.sampleItems
5 | import javax.inject.Inject
6 |
7 | class HiltListScreenModel @Inject constructor() : ScreenModel {
8 |
9 | val items = sampleItems
10 | }
11 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/wasmJsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Compose App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScopedDependencySample.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.kodeinIntegration
2 |
3 | import org.kodein.di.bindings.ScopeCloseable
4 |
5 | data class KodeinScopedDependencySample(
6 | val screenKey: String
7 | ) : ScopeCloseable {
8 | override fun close() {
9 | println("Being disposed")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-core/src/commonJvmMain/kotlin/cafe/adriel/voyager/core/screen/Screen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import androidx.compose.runtime.Composable
4 | import java.io.Serializable
5 |
6 | public actual interface Screen : Serializable {
7 | public actual val key: ScreenKey
8 | get() = commonKeyGeneration()
9 |
10 | @Composable
11 | public actual fun Content()
12 | }
13 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/desktopMain/kotlin/cafe/adriel/voyager/sample/multiplatform/App.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multiplatform
2 |
3 | import androidx.compose.ui.window.Window
4 | import androidx.compose.ui.window.application
5 |
6 | public fun main() {
7 | application {
8 | Window(onCloseRequest = ::exitApplication) {
9 | SampleApplication()
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/macosMain/kotlin/main.macos.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.Window
2 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication
3 | import platform.AppKit.NSApp
4 | import platform.AppKit.NSApplication
5 |
6 | fun main() {
7 | NSApplication.sharedApplication()
8 | Window("VoyagerMultiplatform") {
9 | SampleApplication()
10 | }
11 | NSApp?.run()
12 | }
13 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/AtomicInt32.js.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.atomic
4 |
5 | public actual class AtomicInt32 actual constructor(initialValue: Int) {
6 | private val delegate = atomic(initialValue)
7 | public actual fun getAndIncrement(): Int {
8 | return delegate.incrementAndGet()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/AtomicInt32.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.atomic
4 |
5 | public actual class AtomicInt32 actual constructor(initialValue: Int) {
6 | private val delegate = atomic(initialValue)
7 | public actual fun getAndIncrement(): Int {
8 | return delegate.incrementAndGet()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ListScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.screenModel
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.sample.sampleItems
5 |
6 | class ListScreenModel : ScreenModel {
7 |
8 | val items = sampleItems
9 |
10 | override fun onDispose() {
11 | println("ScreenModel: dispose list")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/PlatformDispatcher.js.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | @InternalVoyagerApi
8 | public actual val PlatformMainDispatcher: CoroutineDispatcher
9 | get() = Dispatchers.Default
10 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/PlatformDispatcher.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | @InternalVoyagerApi
8 | public actual val PlatformMainDispatcher: CoroutineDispatcher
9 | get() = Dispatchers.Main.immediate
10 |
--------------------------------------------------------------------------------
/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.android.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | @InternalVoyagerApi
8 | public actual val PlatformMainDispatcher: CoroutineDispatcher
9 | get() = Dispatchers.Main.immediate
10 |
--------------------------------------------------------------------------------
/voyager-core/src/desktopMain/kotlin/cafe/adriel/voyager/core/concurrent/PlatformDispatcher.desktop.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | @InternalVoyagerApi
8 | public actual val PlatformMainDispatcher: CoroutineDispatcher
9 | get() = Dispatchers.Main.immediate
10 |
--------------------------------------------------------------------------------
/samples/multi-module/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | id("samples-module")
6 | }
7 |
8 | android {
9 | namespace = "cafe.adriel.voyager.sample.multimodule.navigation"
10 | }
11 |
12 | dependencies {
13 | implementation(projects.voyagerCore)
14 | implementation(samplesCatalog.compose.runtime)
15 | }
16 |
--------------------------------------------------------------------------------
/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/internal/Actuals.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.runtime.Composable
5 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
6 |
7 | @InternalVoyagerApi
8 | @Composable
9 | public actual fun BackHandler(enabled: Boolean, onBack: () -> Unit): Unit = BackHandler(enabled, onBack)
10 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/jsMain/kotlin/main.js.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.ExperimentalComposeUiApi
2 | import androidx.compose.ui.window.CanvasBasedWindow
3 | import cafe.adriel.voyager.sample.multiplatform.SampleApplication
4 | import org.jetbrains.skiko.wasm.onWasmReady
5 |
6 | @OptIn(ExperimentalComposeUiApi::class)
7 | fun main() {
8 | onWasmReady {
9 | CanvasBasedWindow("Voyager Sample") {
10 | SampleApplication()
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/mkdocs-requirements.txt:
--------------------------------------------------------------------------------
1 | click==8.1.7
2 | future==1.0.0
3 | Jinja2==3.1.3
4 | livereload==2.6.3
5 | lunr==0.7.0.post1
6 | MarkupSafe==2.1.5
7 | mkdocs==1.5.3
8 | mkdocs-macros-plugin==1.0.5
9 | mkdocs-material==9.5.18
10 | mkdocs-material-extensions==1.3.1
11 | mkdocs-minify-plugin==0.7.1
12 | Pygments==2.17.2
13 | pymdown-extensions==10.8.1
14 | python-dateutil==2.9.0.post0
15 | PyYAML==6.0.1
16 | repackage==0.7.3
17 | six==1.16.0
18 | termcolor==2.4.0
19 | tornado==6.4
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyOneScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidLegacy
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import javax.inject.Inject
5 |
6 | class LegacyOneScreenModel @Inject constructor() : ScreenModel {
7 | val text = "I'm legacy one screen model"
8 |
9 | override fun onDispose() {
10 | println(">>>> disposing $this")
11 | super.onDispose()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyTwoScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidLegacy
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import javax.inject.Inject
5 |
6 | class LegacyTwoScreenModel @Inject constructor() : ScreenModel {
7 | val text = "I'm legacy two screen model"
8 |
9 | override fun onDispose() {
10 | println(">>>> disposing $this")
11 | super.onDispose()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/voyager-livedata/src/main/java/cafe/adriel/voyager/livedata/LiveScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.livedata
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import cafe.adriel.voyager.core.model.ScreenModel
6 |
7 | public abstract class LiveScreenModel(initialState: S) : ScreenModel {
8 |
9 | protected val mutableState: MutableLiveData = MutableLiveData(initialState)
10 | public val state: LiveData = mutableState
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-rxjava/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | id("voyager-jvm-module")
5 | }
6 |
7 | android {
8 | namespace = "cafe.adriel.voyager.rxjava"
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonJvmMain.dependencies {
14 | api(projects.voyagerCore)
15 | api(projects.voyagerScreenmodel)
16 | compileOnly(libs.rxjava)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | * text eol=lf
3 |
4 | # Windows forced line-endings
5 | /.idea/* text eol=crlf
6 | *.bat text eol=crlf
7 | *.ps1 text eol=crlf
8 |
9 | # Gradle wrapper
10 | *.jar binary
11 |
12 | # Images
13 | *.webp binary
14 | *.png binary
15 | *.jpg binary
16 | *.jpeg binary
17 | *.gif binary
18 | *.ico binary
19 | *.icns binary
20 | *.gz binary
21 | *.zip binary
22 | *.7z binary
23 | *.ttf binary
24 | *.eot binary
25 | *.woff binary
26 | *.pyc binary
27 | *.swp binary
28 | *.pdf binary
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenProvider.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.registry
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | @Composable
8 | public inline fun rememberScreen(provider: T): Screen =
9 | remember(provider) {
10 | ScreenRegistry.get(provider)
11 | }
12 |
13 | public interface ScreenProvider
14 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp.xcodeproj/xcuserdata/gabriel.lopes.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iosApp.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/voyager-livedata/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | id("voyager-android-module")
5 | }
6 |
7 | android {
8 | namespace = "cafe.adriel.voyager.livedata"
9 | }
10 |
11 | dependencies {
12 | api(projects.voyagerCore)
13 | api(projects.voyagerScreenmodel)
14 |
15 | implementation(libs.androidx.lifecycle.livedata)
16 |
17 | testRuntimeOnly(libs.junit.engine)
18 | testImplementation(libs.junit.api)
19 | }
20 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/java/cafe/adriel/voyager/sample/multimodule/SampleApp.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule
2 |
3 | import android.app.Application
4 | import cafe.adriel.voyager.core.registry.ScreenRegistry
5 | import cafe.adriel.voyager.sample.multimodule.posts.featurePostsScreenModule
6 |
7 | class SampleApp : Application() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 |
12 | ScreenRegistry {
13 | featurePostsScreenModule()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Voyager Sample - Compose Multiplatform
6 |
7 |
8 |
9 |
10 | Voyager Sample - Compose Multiplatform
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.NonRestartableComposable
5 | import androidx.compose.runtime.Stable
6 |
7 | @Composable
8 | @NonRestartableComposable
9 | internal expect fun getConfigurationChecker(): ConfigurationChecker
10 |
11 | @Stable
12 | internal expect class ConfigurationChecker {
13 | fun isChangingConfigurations(): Boolean
14 | }
15 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/screen/Screen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.screen
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.platform.multiplatformName
5 |
6 | public expect interface Screen {
7 |
8 | public open val key: ScreenKey
9 |
10 | @Composable
11 | public fun Content()
12 | }
13 |
14 | internal fun Screen.commonKeyGeneration() =
15 | this::class.multiplatformName ?: error("Default ScreenKey not found, please provide your own key")
16 |
--------------------------------------------------------------------------------
/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ScreenModule.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule.posts
2 |
3 | import cafe.adriel.voyager.core.registry.screenModule
4 | import cafe.adriel.voyager.sample.multimodule.navigation.SharedScreen
5 |
6 | val featurePostsScreenModule = screenModule {
7 | register {
8 | ListScreen()
9 | }
10 | register { provider ->
11 | DetailsScreen(id = provider.id)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/kotlin/cafe/adriel/voyager/sample/multiplatform/SampleActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multiplatform
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 |
7 | public class SampleActivity : ComponentActivity() {
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 |
11 | setContent {
12 | SampleApplication()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeSet.native.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.locks.SynchronizedObject
4 |
5 | public actual class ThreadSafeSet(
6 | syncObject: SynchronizedObject,
7 | delegate: MutableSet
8 | ) : MutableSet, ThreadSafeMutableCollection(syncObject, delegate) {
9 | public actual constructor() : this(delegate = mutableSetOf())
10 | public constructor(delegate: MutableSet) : this(SynchronizedObject(), delegate)
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/Serializable.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | /**
4 | * Multiplatform reference to Java `Serializable` interface.
5 | *
6 | * Any object that will be pass to a Screen as parameter, on Android target, requires to be serializable in a
7 | * Bundle, so if your project is multiplatform and targeting Android, to prevent State Restoration issues on Android
8 | * the easiest way is to apply this interface to your Models.
9 | */
10 | public expect interface JavaSerializable
11 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelFactoryKey.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | import dagger.MapKey
4 | import kotlin.reflect.KClass
5 |
6 | /**
7 | * A Dagger multibinding key used to identify a [ScreenModelFactory]
8 | */
9 | @MustBeDocumented
10 | @Target(
11 | AnnotationTarget.FUNCTION,
12 | AnnotationTarget.PROPERTY_GETTER,
13 | AnnotationTarget.PROPERTY_SETTER
14 | )
15 | @Retention(AnnotationRetention.RUNTIME)
16 | @MapKey
17 | public annotation class ScreenModelFactoryKey(val value: KClass)
18 |
--------------------------------------------------------------------------------
/samples/multi-module/feature-home/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | id("samples-module")
6 | }
7 |
8 | android {
9 | namespace = "cafe.adriel.voyager.sample.multimodule.home"
10 | }
11 |
12 | dependencies {
13 | implementation(projects.voyagerNavigator)
14 |
15 | implementation(projects.samples.multiModule.navigation)
16 |
17 | implementation(libs.androidx.activity.compose)
18 | implementation(samplesCatalog.compose.material)
19 | }
20 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ScreenModelActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.screenModel
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class ScreenModelActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(ListScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelKey.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import dagger.MapKey
5 | import kotlin.reflect.KClass
6 |
7 | /**
8 | * A Dagger multibinding key used to identify a [ScreenModel]
9 | */
10 | @MustBeDocumented
11 | @Target(
12 | AnnotationTarget.FUNCTION,
13 | AnnotationTarget.PROPERTY_GETTER,
14 | AnnotationTarget.PROPERTY_SETTER
15 | )
16 | @Retention(AnnotationRetention.RUNTIME)
17 | @MapKey
18 | public annotation class ScreenModelKey(val value: KClass)
19 |
--------------------------------------------------------------------------------
/voyager-transitions/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.transitions"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerNavigator)
18 | implementation(compose.animation)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinIntegrationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.koinIntegration
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class KoinIntegrationActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(KoinScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/voyager-rxjava/api/android/voyager-rxjava.api:
--------------------------------------------------------------------------------
1 | public abstract class cafe/adriel/voyager/rxjava/RxScreenModel : cafe/adriel/voyager/core/model/ScreenModel {
2 | public fun ()V
3 | protected final fun getMutableState ()Lio/reactivex/rxjava3/subjects/BehaviorSubject;
4 | public final fun getState ()Lio/reactivex/rxjava3/core/Observable;
5 | public fun onDispose ()V
6 | }
7 |
8 | public final class cafe/adriel/voyager/rxjava/ScreenModelKt {
9 | public static final fun getDisposables (Lcafe/adriel/voyager/core/model/ScreenModel;)Lio/reactivex/rxjava3/disposables/CompositeDisposable;
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/voyager-rxjava/api/desktop/voyager-rxjava.api:
--------------------------------------------------------------------------------
1 | public abstract class cafe/adriel/voyager/rxjava/RxScreenModel : cafe/adriel/voyager/core/model/ScreenModel {
2 | public fun ()V
3 | protected final fun getMutableState ()Lio/reactivex/rxjava3/subjects/BehaviorSubject;
4 | public final fun getState ()Lio/reactivex/rxjava3/core/Observable;
5 | public fun onDispose ()V
6 | }
7 |
8 | public final class cafe/adriel/voyager/rxjava/ScreenModelKt {
9 | public static final fun getDisposables (Lcafe/adriel/voyager/core/model/ScreenModel;)Lio/reactivex/rxjava3/disposables/CompositeDisposable;
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/docs/back-press.md:
--------------------------------------------------------------------------------
1 | # Back press
2 |
3 | By default, Voyager will handle back presses but you can override its behavior. Use the `onBackPressed` to manually handle it: return `true` to pop the current screen, or false otherwise. To disable, just set to `null`.
4 |
5 | ```kotlin
6 | setContent {
7 | Navigator(
8 | initialScreen = HomeScreen,
9 | onBackPressed = { currentScreen ->
10 | false // won't pop the current screen
11 | // true will pop, default behavior
12 | }
13 | // To disable:
14 | // onBackPressed = null
15 | )
16 | }
17 | ```
18 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinIntegrationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.kodeinIntegration
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class KodeinIntegrationActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(KodeinScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaIntegrationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.rxJavaIntegration
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class RxJavaIntegrationActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(RxJavaScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/voyager-navigator/src/nonAndroidMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.nonAndroid.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider
4 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | internal actual class DefaultNavigatorScreenLifecycleProvider actual constructor() : NavigatorScreenLifecycleProvider {
8 | actual override fun provide(screen: Screen): List =
9 | emptyList()
10 | }
11 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidViewModelActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidViewModel
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class AndroidViewModelActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(AndroidListScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataIntegrationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.liveDataIntegration
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 |
8 | class LiveDataIntegrationActivity : ComponentActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 |
13 | setContent {
14 | Navigator(LiveDataScreen())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import ComposeApp
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | MainViewControllerKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
17 | }
18 | }
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/api/android/voyager-lifecycle-kmp.api:
--------------------------------------------------------------------------------
1 | public final class cafe/adriel/voyager/jetpack/LifecycleProviderKt {
2 | public static final fun ProvideNavigatorLifecycleKMPSupport (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
3 | }
4 |
5 | public final class cafe/adriel/voyager/jetpack/NavigatorKt {
6 | public static final fun rememberNavigatorViewModelStoreOwner (Lcafe/adriel/voyager/navigator/Navigator;Landroidx/compose/runtime/Composer;I)Landroidx/lifecycle/ViewModelStoreOwner;
7 | }
8 |
9 | public final class cafe/adriel/voyager/jetpack/VoyagerLifecycleKMPOwner$Companion {
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/api/desktop/voyager-lifecycle-kmp.api:
--------------------------------------------------------------------------------
1 | public final class cafe/adriel/voyager/jetpack/LifecycleProviderKt {
2 | public static final fun ProvideNavigatorLifecycleKMPSupport (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
3 | }
4 |
5 | public final class cafe/adriel/voyager/jetpack/NavigatorKt {
6 | public static final fun rememberNavigatorViewModelStoreOwner (Lcafe/adriel/voyager/navigator/Navigator;Landroidx/compose/runtime/Composer;I)Landroidx/lifecycle/ViewModelStoreOwner;
7 | }
8 |
9 | public final class cafe/adriel/voyager/jetpack/VoyagerLifecycleKMPOwner$Companion {
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/samples/multi-module/feature-posts/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | id("samples-module")
6 | }
7 |
8 | android {
9 | namespace = "cafe.adriel.voyager.sample.multimodule.posts"
10 | }
11 |
12 | dependencies {
13 | implementation(projects.voyagerScreenmodel)
14 | implementation(projects.voyagerNavigator)
15 |
16 | implementation(projects.samples.multiModule.navigation)
17 |
18 | implementation(libs.androidx.activity.compose)
19 | implementation(samplesCatalog.compose.material)
20 | }
21 |
--------------------------------------------------------------------------------
/voyager-tab-navigator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.navigator.tab"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerNavigator)
18 | implementation(compose.runtime)
19 | implementation(compose.ui)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/includes.gradle.kts:
--------------------------------------------------------------------------------
1 | include(
2 | ":samples:android",
3 | ":samples:multiplatform",
4 |
5 | ":samples:multi-module:app",
6 | ":samples:multi-module:navigation",
7 | ":samples:multi-module:feature-home",
8 | ":samples:multi-module:feature-posts",
9 |
10 | ":voyager-core",
11 | ":voyager-screenmodel",
12 | ":voyager-navigator",
13 | ":voyager-lifecycle-kmp",
14 | ":voyager-tab-navigator",
15 | ":voyager-bottom-sheet-navigator",
16 | ":voyager-transitions",
17 | ":voyager-hilt",
18 | ":voyager-kodein",
19 | ":voyager-koin",
20 | ":voyager-rxjava",
21 | ":voyager-livedata"
22 | )
23 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/java/cafe/adriel/voyager/sample/multimodule/SampleActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 | import cafe.adriel.voyager.sample.multimodule.home.HomeScreen
8 |
9 | class SampleActivity : ComponentActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 |
14 | setContent {
15 | Navigator(HomeScreen())
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltMainActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import cafe.adriel.voyager.navigator.Navigator
7 | import dagger.hilt.android.AndroidEntryPoint
8 |
9 | @AndroidEntryPoint
10 | class HiltMainActivity : ComponentActivity() {
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 |
15 | setContent {
16 | Navigator(HiltListScreen())
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/docs/deep-links.md:
--------------------------------------------------------------------------------
1 | # Deep links
2 |
3 | !!! warning
4 | Currently Voyager does not provided a built in solution to handle Deeplink and URIs. see [#149](https://github.com/adrielcafe/voyager/issues/149) and [#382](https://github.com/adrielcafe/voyager/issues/382)
5 |
6 | You can initialize the `Navigator` with multiple screens, that way, the first visible screen will be the last one and will be possible to return (`pop()`) to the previous screens.
7 |
8 | ```kotlin
9 | val postId = getPostIdFromIntent()
10 |
11 | setContent {
12 | Navigator(
13 | HomeScreen,
14 | PostListScreen(),
15 | PostDetailsScreen(postId)
16 | )
17 | }
18 | ```
19 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.assisted.Assisted
5 | import dagger.assisted.AssistedFactory
6 | import dagger.assisted.AssistedInject
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 |
9 | @HiltViewModel(assistedFactory = HiltDetailsViewModel.Factory::class)
10 | class HiltDetailsViewModel @AssistedInject constructor(
11 | @Assisted val index: Int
12 | ) : ViewModel() {
13 |
14 | @AssistedFactory
15 | interface Factory {
16 | fun create(index: Int): HiltDetailsViewModel
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/Application.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multiplatform
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.navigator.Navigator
5 | import cafe.adriel.voyager.transitions.SlideTransition
6 |
7 | @Composable
8 | public fun SampleApplication() {
9 | Navigator(
10 | screen = BasicNavigationScreen(index = 0),
11 | onBackPressed = { currentScreen ->
12 | println("Navigator: Pop screen #${(currentScreen as BasicNavigationScreen).index}")
13 | true
14 | }
15 | ) { navigator ->
16 | SlideTransition(navigator)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/voyager-screenmodel/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.screenmodel"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerNavigator)
18 | implementation(compose.runtime)
19 | implementation(compose.runtimeSaveable)
20 | implementation(libs.coroutines.core)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.android.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import cafe.adriel.voyager.androidx.AndroidScreenLifecycleOwner
4 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider
5 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider
6 | import cafe.adriel.voyager.core.screen.Screen
7 |
8 | internal actual class DefaultNavigatorScreenLifecycleProvider actual constructor() : NavigatorScreenLifecycleProvider {
9 | actual override fun provide(screen: Screen): List =
10 | listOf(AndroidScreenLifecycleOwner.get(screen))
11 | }
12 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/lifecycle/ConfigurationChecker.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.NonRestartableComposable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.runtime.remember
7 |
8 | private val configurationChecker = ConfigurationChecker()
9 |
10 | @Composable
11 | @NonRestartableComposable
12 | internal actual fun getConfigurationChecker(): ConfigurationChecker {
13 | return remember { configurationChecker }
14 | }
15 |
16 | @Stable
17 | internal actual class ConfigurationChecker {
18 | actual fun isChangingConfigurations(): Boolean = false
19 | }
20 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/lifecycle/ConfigurationChecker.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.NonRestartableComposable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.runtime.remember
7 |
8 | private val configurationChecker = ConfigurationChecker()
9 |
10 | @Composable
11 | @NonRestartableComposable
12 | internal actual fun getConfigurationChecker(): ConfigurationChecker {
13 | return remember { configurationChecker }
14 | }
15 |
16 | @Stable
17 | internal actual class ConfigurationChecker {
18 | actual fun isChangingConfigurations(): Boolean = false
19 | }
20 |
--------------------------------------------------------------------------------
/voyager-core/src/desktopMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.NonRestartableComposable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.runtime.remember
7 |
8 | private val configurationChecker = ConfigurationChecker()
9 |
10 | @Composable
11 | @NonRestartableComposable
12 | internal actual fun getConfigurationChecker(): ConfigurationChecker {
13 | return remember { configurationChecker }
14 | }
15 |
16 | @Stable
17 | internal actual class ConfigurationChecker {
18 | actual fun isChangingConfigurations(): Boolean = false
19 | }
20 |
--------------------------------------------------------------------------------
/voyager-navigator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.navigator"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | implementation(compose.runtime)
18 | implementation(compose.runtimeSaveable)
19 | }
20 | androidMain.dependencies {
21 | implementation(libs.androidx.activity.compose)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration/KoinScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.koinIntegration
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.screen.Screen
5 | import cafe.adriel.voyager.core.screen.ScreenKey
6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
7 | import cafe.adriel.voyager.koin.koinScreenModel
8 | import cafe.adriel.voyager.sample.ListContent
9 |
10 | class KoinScreen : Screen {
11 |
12 | override val key: ScreenKey = uniqueScreenKey
13 |
14 | @Composable
15 | override fun Content() {
16 | val screenModel = koinScreenModel()
17 |
18 | ListContent(screenModel.items)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/voyager-kodein/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.kodein"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerScreenmodel)
18 | api(projects.voyagerNavigator)
19 | implementation(libs.kodein)
20 | implementation(compose.runtime)
21 | implementation(compose.runtimeSaveable)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidListViewModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidViewModel
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import androidx.lifecycle.ViewModel
5 | import cafe.adriel.voyager.sample.sampleItems
6 |
7 | class AndroidListViewModel(private val handle: SavedStateHandle) : ViewModel() {
8 |
9 | init {
10 | if (handle.get>("items").isNullOrEmpty()) {
11 | handle["items"] = sampleItems
12 | }
13 | }
14 |
15 | val items: List
16 | get() = handle["items"] ?: error("Items not found")
17 |
18 | override fun onCleared() {
19 | println("ViewModel: clear list")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/NavigatorScreenLifecycle.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.ProvidableCompositionLocal
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | public val LocalNavigatorScreenLifecycleProvider: ProvidableCompositionLocal =
8 | staticCompositionLocalOf { null }
9 |
10 | /**
11 | * Can provides a list of ScreenLifecycleOwner for each Screen in the Navigator stack.
12 | */
13 | public interface NavigatorScreenLifecycleProvider {
14 |
15 | public fun provide(screen: Screen): List
16 | }
17 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.hilt.ScreenModelFactory
5 | import dagger.assisted.Assisted
6 | import dagger.assisted.AssistedFactory
7 | import dagger.assisted.AssistedInject
8 |
9 | // Working with Assisted Injection here to simulate a custom param in the constructor
10 | class HiltDetailsScreenModel @AssistedInject constructor(
11 | @Assisted val index: Int
12 | ) : ScreenModel {
13 |
14 | @AssistedFactory
15 | interface Factory : ScreenModelFactory {
16 | fun create(index: Int): HiltDetailsScreenModel
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeMap.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | public expect class ThreadSafeMap() : MutableMap {
4 | override fun clear()
5 | override fun put(key: K, value: V): V?
6 | override fun putAll(from: Map)
7 | override fun remove(key: K): V?
8 | override val entries: MutableSet>
9 | override val keys: MutableSet
10 | override val values: MutableCollection
11 | override fun containsKey(key: K): Boolean
12 | override fun containsValue(value: V): Boolean
13 | override fun get(key: K): V?
14 | override fun isEmpty(): Boolean
15 | override val size: Int
16 | }
17 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeSet.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | public expect class ThreadSafeSet() : MutableSet {
4 | override fun add(element: T): Boolean
5 | override fun addAll(elements: Collection): Boolean
6 | override fun clear()
7 | override fun iterator(): MutableIterator
8 | override fun remove(element: T): Boolean
9 | override fun removeAll(elements: Collection): Boolean
10 | override fun retainAll(elements: Collection): Boolean
11 | override fun contains(element: T): Boolean
12 | override fun containsAll(elements: Collection): Boolean
13 | override fun isEmpty(): Boolean
14 | override val size: Int
15 | }
16 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/internal/SafeCollections.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.internal
2 |
3 | /**
4 | * https://youtrack.jetbrains.com/issue/KT-71375/Prevent-Kotlins-removeFirst-and-removeLast-from-causing-crashes-on-Android-14-and-below-after-upgrading-to-Android-API-Level-35
5 | */
6 |
7 | internal fun MutableList.removeFirstElement(): T =
8 | if (isEmpty()) throw NoSuchElementException("List is empty.") else removeAt(0)
9 |
10 | internal fun MutableList.removeFirstElementOrNull(): T? =
11 | if (isEmpty()) null else removeAt(0)
12 |
13 | internal fun MutableList.removeLastElement(): T =
14 | if (isEmpty()) throw NoSuchElementException("List is empty.") else removeAt(lastIndex)
15 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListViewModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import androidx.lifecycle.ViewModel
5 | import cafe.adriel.voyager.sample.sampleItems
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import javax.inject.Inject
8 |
9 | @HiltViewModel
10 | class HiltListViewModel @Inject constructor(
11 | private val handle: SavedStateHandle
12 | ) : ViewModel() {
13 |
14 | init {
15 | if (handle.get>("items").isNullOrEmpty()) {
16 | handle["items"] = sampleItems
17 | }
18 | }
19 |
20 | val items: List
21 | get() = handle["items"] ?: error("Items not found")
22 | }
23 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.navigator.bottomSheet"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerNavigator)
18 | implementation(compose.runtime)
19 | implementation(compose.material)
20 | }
21 | androidMain.dependencies {
22 | implementation(libs.androidx.activity.compose)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/voyager-tab-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/tab/Tab.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.tab
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | @Composable
8 | public fun CurrentTab() {
9 | val tabNavigator = LocalTabNavigator.current
10 | val currentTab = tabNavigator.current
11 |
12 | tabNavigator.saveableState("currentTab") {
13 | currentTab.Content()
14 | }
15 | }
16 |
17 | public data class TabOptions(
18 | val index: UShort,
19 | val title: String,
20 | val icon: Painter? = null
21 | )
22 |
23 | public interface Tab : Screen {
24 |
25 | public val options: TabOptions
26 | @Composable get
27 | }
28 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/internal/ContextExt.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt.internal
2 |
3 | import android.content.Context
4 | import android.content.ContextWrapper
5 | import androidx.activity.ComponentActivity
6 |
7 | private inline fun findOwner(context: Context): T? {
8 | var innerContext = context
9 | while (innerContext is ContextWrapper) {
10 | if (innerContext is T) {
11 | return innerContext
12 | }
13 | innerContext = innerContext.baseContext
14 | }
15 | return null
16 | }
17 |
18 | @PublishedApi
19 | internal val Context.componentActivity: ComponentActivity
20 | get() = findOwner(this)
21 | ?: error("Context must be a androidx.activity.ComponentActivity. Current is $this")
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Gradle
2 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
3 |
4 | # Android
5 | android.useAndroidX=true
6 | android.defaults.buildfeatures.buildConfig=false
7 | android.library.defaults.buildfeatures.androidresources=false
8 |
9 | # Kotlin
10 | kotlin.code.style=official
11 | kotlin.native.useEmbeddableCompilerJar=true
12 | kotlin.native.ignoreDisabledTargets=true
13 | kotlin.mpp.stability.nowarn=true
14 |
15 | # Compose multiplatform
16 | org.jetbrains.compose.experimental.macos.enabled=true
17 | org.jetbrains.compose.experimental.jscanvas.enabled=true
18 | org.jetbrains.compose.experimental.wasm.enabled=true
19 |
20 | # Atomicfu
21 | kotlinx.atomicfu.enableJvmIrTransformation=true
22 | kotlinx.atomicfu.enableNativeIrTransformation=true
23 | kotlinx.atomicfu.enableJsIrTransformation=true
24 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidLegacy
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.compose.ui.platform.ComposeView
6 | import cafe.adriel.voyager.navigator.Navigator
7 | import cafe.adriel.voyager.sample.R
8 | import dagger.hilt.android.AndroidEntryPoint
9 |
10 | @AndroidEntryPoint
11 | class LegacyActivity : ComponentActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_legacy)
15 | val composeView = findViewById(R.id.composeView)
16 | composeView.setContent {
17 | Navigator(LegacyScreenOne())
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/samples/multiplatform/README.md:
--------------------------------------------------------------------------------
1 | # multiplatform sample
2 |
3 | ### Running iOS
4 | - Open Xcode project with: `open samples/multiplatform-iosApp/iosApp.xcodeproj`
5 | - Run/Build in Xcode
6 |
7 | ### Running MacOS Native app (Desktop using Kotlin Native)
8 | ```shell
9 | ./gradlew :samples:multiplatform:runNativeDebug
10 | ```
11 |
12 | ### Running JVM Native app (Desktop)
13 | ```shell
14 | ./gradlew :samples:multiplatform:run
15 | ```
16 |
17 | ### Running Web Compose Canvas
18 | ```shell
19 | ./gradlew :samples:multiplatform:jsBrowserDevelopmentRun
20 | ```
21 |
22 | ### Building Android App
23 | ```shell
24 | ./gradlew :samples:multiplatform:assembleDebug
25 | ```
26 |
27 | If you want to run Android sample in the emulator, you can open the project and run the application configuration `samples.multiplatform` on Android Studio.
28 |
--------------------------------------------------------------------------------
/voyager-hilt/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.kotlin.kapt)
6 | id("voyager-android-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.hilt"
11 | }
12 |
13 | kapt {
14 | correctErrorTypes = true
15 | }
16 |
17 | dependencies {
18 | api(projects.voyagerScreenmodel)
19 | api(projects.voyagerNavigator)
20 |
21 | implementation(libs.androidx.lifecycle.savedState)
22 | implementation(libs.androidx.lifecycle.viewModelKtx)
23 | implementation(libs.androidx.lifecycle.viewModelCompose)
24 | implementation(libs.hilt.android)
25 | kapt(libs.hilt.compiler)
26 |
27 | testRuntimeOnly(libs.junit.engine)
28 | testImplementation(libs.junit.api)
29 | }
30 |
--------------------------------------------------------------------------------
/samples/multi-module/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | id("samples-module")
6 | }
7 |
8 | android {
9 | namespace = "cafe.adriel.voyager.sample.multimodule"
10 | defaultConfig {
11 | applicationId = "cafe.adriel.voyager.sample.multimodule"
12 | }
13 | }
14 |
15 | dependencies {
16 | implementation(projects.voyagerNavigator)
17 |
18 | implementation(projects.samples.multiModule.featureHome)
19 | implementation(projects.samples.multiModule.featurePosts)
20 |
21 | implementation(libs.androidx.activity.compose)
22 | implementation(samplesCatalog.compose.runtime)
23 | implementation(samplesCatalog.compose.material)
24 |
25 | debugImplementation(samplesCatalog.leakCanary)
26 | }
27 |
--------------------------------------------------------------------------------
/gradle/samples.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | leakCanary = "2.14"
3 | koin = "4.1.0"
4 | compose = "1.8.3"
5 | composeMaterialIcons = "1.7.8"
6 |
7 | [libraries]
8 | leakCanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanary" }
9 | koin = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
10 | compose-rxjava = { module = "androidx.compose.runtime:runtime-rxjava3", version.ref = "compose" }
11 | compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" }
12 | compose-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" }
13 | compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
14 | compose-materialIcons = { module = "androidx.compose.material:material-icons-core", version.ref = "composeMaterialIcons" }
15 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/bottomSheetNavigation/BottomSheetNavigationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.bottomSheetNavigation
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.material.ExperimentalMaterialApi
7 | import cafe.adriel.voyager.navigator.Navigator
8 | import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
9 |
10 | class BottomSheetNavigationActivity : ComponentActivity() {
11 |
12 | @OptIn(ExperimentalMaterialApi::class)
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 |
16 | setContent {
17 | BottomSheetNavigator {
18 | Navigator(BackScreen())
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.liveDataIntegration
2 |
3 | import cafe.adriel.voyager.core.model.screenModelScope
4 | import cafe.adriel.voyager.livedata.LiveScreenModel
5 | import cafe.adriel.voyager.sample.sampleItems
6 | import kotlinx.coroutines.delay
7 | import kotlinx.coroutines.launch
8 |
9 | class LiveDataScreenModel : LiveScreenModel(State.Loading) {
10 |
11 | sealed class State {
12 | object Loading : State()
13 | data class Result(val items: List) : State()
14 | }
15 |
16 | private val items = sampleItems
17 |
18 | fun getItems() {
19 | screenModelScope.launch {
20 | delay(1_000)
21 | mutableState.postValue(State.Result(items))
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/voyager-koin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.koin"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerScreenmodel)
18 | api(projects.voyagerNavigator)
19 |
20 | implementation(compose.runtime)
21 | implementation(compose.runtimeSaveable)
22 |
23 | implementation(libs.coroutines.core)
24 | }
25 | }
26 | }
27 |
28 | dependencies {
29 | commonMainImplementation(libs.koin.compose) {
30 | exclude("org.jetbrains.compose.runtime")
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidLegacy/LegacyModule.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidLegacy
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.hilt.ScreenModelKey
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.components.ActivityComponent
9 | import dagger.multibindings.IntoMap
10 |
11 | @Module
12 | @InstallIn(ActivityComponent::class)
13 | abstract class LegacyModule {
14 | @Binds
15 | @IntoMap
16 | @ScreenModelKey(LegacyOneScreenModel::class)
17 | abstract fun bindLegacyOneScreenModel(legacyOneScreenModel: LegacyOneScreenModel): ScreenModel
18 |
19 | @Binds
20 | @IntoMap
21 | @ScreenModelKey(LegacyTwoScreenModel::class)
22 | abstract fun bindLegacyTwoScreenModel(legacyTwoScreenModel: LegacyTwoScreenModel): ScreenModel
23 | }
24 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/DetailsScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.screenModel
2 |
3 | import cafe.adriel.voyager.core.model.StateScreenModel
4 | import cafe.adriel.voyager.core.model.screenModelScope
5 | import kotlinx.coroutines.delay
6 | import kotlinx.coroutines.launch
7 |
8 | class DetailsScreenModel(
9 | val index: Int
10 | ) : StateScreenModel(State.Loading) {
11 |
12 | sealed class State {
13 | object Loading : State()
14 | data class Result(val item: String) : State()
15 | }
16 |
17 | fun getItem(index: Int) {
18 | screenModelScope.launch {
19 | delay(1_000)
20 | mutableState.value = State.Result("Item #$index")
21 | }
22 | }
23 |
24 | override fun onDispose() {
25 | println("ScreenModel: dispose details")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/OptionalMultibindingsModule.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.android.components.ActivityRetainedComponent
7 | import dagger.multibindings.Multibinds
8 |
9 | /**
10 | * By default Dagger Multibindings are required to have at least one definition.
11 | * This module says to Dagger that a map with [ScreenModel] or [ScreenModelFactory] can be empty.
12 | */
13 | @Module
14 | @InstallIn(ActivityRetainedComponent::class)
15 | public abstract class OptionalMultibindingsModule {
16 | @Multibinds
17 | public abstract fun screenModelFactories(): Map, ScreenModelFactory>
18 |
19 | @Multibinds
20 | public abstract fun screenModels(): Map, ScreenModel>
21 | }
22 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/basicNavigation/BasicNavigationActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.basicNavigation
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import cafe.adriel.voyager.navigator.Navigator
8 |
9 | class BasicNavigationActivity : ComponentActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 |
14 | setContent {
15 | Navigator(
16 | screen = BasicNavigationScreen(index = 0),
17 | onBackPressed = { currentScreen ->
18 | Log.d("Navigator", "Pop screen #${(currentScreen as BasicNavigationScreen).index}")
19 | true
20 | }
21 | )
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/voyager-bottom-sheet-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/bottomSheet/internal/BottomSheetNavigatorBackHandler.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.bottomSheet.internal
2 |
3 | import androidx.compose.material.ExperimentalMaterialApi
4 | import androidx.compose.material.ModalBottomSheetState
5 | import androidx.compose.runtime.Composable
6 | import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
7 |
8 | @Composable
9 | internal expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
10 |
11 | @ExperimentalMaterialApi
12 | @Composable
13 | internal fun BottomSheetNavigatorBackHandler(
14 | navigator: BottomSheetNavigator,
15 | sheetState: ModalBottomSheetState,
16 | hideOnBackPress: Boolean
17 | ) {
18 | BackHandler(enabled = sheetState.isVisible) {
19 | if (navigator.pop().not() && hideOnBackPress) {
20 | navigator.hide()
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/docs/android-viewmodel/index.md:
--------------------------------------------------------------------------------
1 | # Android ViewModel
2 |
3 | ```kotlin
4 | class PostListScreen : Screen {
5 |
6 | @Composable
7 | override fun Content() {
8 | val viewModel = viewModel()
9 | // ...
10 | }
11 | }
12 | ```
13 |
14 | By default Voyager provides its own `LocalViewModelStoreOwner` and `LocalSavedStateRegistryOwner`, that way you can safely create `ViewModel`s without depending on `Activity` or `Fragment`.
15 |
16 | !!! info
17 | Voyager provides a similar implementation, the [ScreenModel](../screenmodel/README.md), which does the same as `ViewModel` but also works with [Compose Multiplatform](https://github.com/jetbrains/compose-jb).
18 |
19 | ### Sample
20 |
21 | 
22 |
23 | !!! info
24 | Source code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel).
25 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltListScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.screen.Screen
5 | import cafe.adriel.voyager.hilt.getViewModel
6 | import cafe.adriel.voyager.navigator.LocalNavigator
7 | import cafe.adriel.voyager.navigator.currentOrThrow
8 | import cafe.adriel.voyager.sample.ListContent
9 |
10 | class HiltListScreen : Screen {
11 |
12 | @Composable
13 | override fun Content() {
14 | val navigator = LocalNavigator.currentOrThrow
15 | val viewModel: HiltListViewModel = getViewModel()
16 |
17 | // Uncomment version below if you want to use ScreenModel
18 | // val viewModel: HiltListScreenModel = getScreenModel()
19 |
20 | ListContent(viewModel.items, onClick = { index -> navigator.push(HiltDetailsScreen(index)) })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/voyager-rxjava/src/commonJvmMain/kotlin/cafe/adriel/voyager/rxjava/ScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.rxjava
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.core.model.ScreenModelStore
5 | import io.reactivex.rxjava3.core.Observable
6 | import io.reactivex.rxjava3.disposables.CompositeDisposable
7 | import io.reactivex.rxjava3.subjects.BehaviorSubject
8 |
9 | public val ScreenModel.disposables: CompositeDisposable
10 | get() = ScreenModelStore.getOrPutDependency(
11 | screenModel = this,
12 | name = "ScreenModelCompositeDisposable",
13 | factory = { CompositeDisposable() },
14 | onDispose = { disposables -> disposables.clear() }
15 | )
16 |
17 | public abstract class RxScreenModel : ScreenModel {
18 |
19 | protected val mutableState: BehaviorSubject = BehaviorSubject.create()
20 | public val state: Observable = mutableState
21 | }
22 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/parcelableScreen/ParcelableActivity.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.parcelableScreen
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import cafe.adriel.voyager.navigator.Navigator
8 |
9 | class ParcelableActivity : ComponentActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 |
14 | setContent {
15 | Navigator(
16 | screen = SampleParcelableScreen(parcelable = ParcelableContent(index = 0)),
17 | onBackPressed = { currentScreen ->
18 | Log.d("Navigator", "Pop screen #${(currentScreen as SampleParcelableScreen).parcelable.index}")
19 | true
20 | }
21 | )
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/nonAndroidMain/kotlin/cafe/adriel/voyager/jetpack/AndroidScreenLifecycleOwner.nonAndroid.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.ProvidedValue
5 | import androidx.lifecycle.ViewModelProvider
6 | import androidx.lifecycle.viewmodel.MutableCreationExtras
7 | import androidx.savedstate.SavedStateRegistryOwner
8 |
9 | internal actual class SavedStateViewModelPlatform actual constructor(
10 | owner: SavedStateRegistryOwner
11 | ) {
12 | public actual fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
13 | return object : ViewModelProvider.Factory {
14 | }
15 | }
16 |
17 | public actual fun providePlatform(extras: MutableCreationExtras) {}
18 |
19 | @Composable
20 | public actual fun initHooks() {}
21 |
22 | public actual fun provideHooks(): List> = emptyList()
23 | }
24 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/ListScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.screenModel
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.model.rememberScreenModel
5 | import cafe.adriel.voyager.core.screen.Screen
6 | import cafe.adriel.voyager.core.screen.ScreenKey
7 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
8 | import cafe.adriel.voyager.navigator.LocalNavigator
9 | import cafe.adriel.voyager.navigator.currentOrThrow
10 | import cafe.adriel.voyager.sample.ListContent
11 |
12 | class ListScreen : Screen {
13 |
14 | override val key: ScreenKey = uniqueScreenKey
15 |
16 | @Composable
17 | override fun Content() {
18 | val navigator = LocalNavigator.currentOrThrow
19 | val screenModel = rememberScreenModel { ListScreenModel() }
20 |
21 | ListContent(screenModel.items, onClick = { index -> navigator.push(DetailsScreen(index)) })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | branches: [main]
7 |
8 | jobs:
9 |
10 | build:
11 | name: Build
12 | runs-on: macOS-latest
13 | timeout-minutes: 60
14 | steps:
15 |
16 | - name: Fetch Sources
17 | uses: actions/checkout@v3
18 |
19 | - name: Copy CI gradle.properties
20 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
21 |
22 | - name: Setup Java
23 | uses: actions/setup-java@v3
24 | with:
25 | java-version: 17
26 | distribution: 'temurin'
27 |
28 | - name: Lint
29 | run: |
30 | ./gradlew ktlintCheck --stacktrace
31 |
32 | - name: Api Check
33 | run: |
34 | ./gradlew apiCheck --stacktrace
35 |
36 | - name: Validate publish
37 | run: |
38 | ./gradlew -Pversion=1.0.0-SNAPSHOT publishToMavenLocal --stacktrace
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidListScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidViewModel
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.lifecycle.viewmodel.compose.viewModel
5 | import cafe.adriel.voyager.core.screen.Screen
6 | import cafe.adriel.voyager.core.screen.ScreenKey
7 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
8 | import cafe.adriel.voyager.navigator.LocalNavigator
9 | import cafe.adriel.voyager.navigator.currentOrThrow
10 | import cafe.adriel.voyager.sample.ListContent
11 |
12 | class AndroidListScreen : Screen {
13 | override val key: ScreenKey = uniqueScreenKey
14 |
15 | @Composable
16 | override fun Content() {
17 | val navigator = LocalNavigator.currentOrThrow
18 | val viewModel = viewModel()
19 |
20 | ListContent(viewModel.items, onClick = { index -> navigator.push(AndroidDetailsScreen(index)) })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/docs-publish.yaml:
--------------------------------------------------------------------------------
1 | name: Docs Publish
2 | on:
3 | push:
4 | branches: [main]
5 |
6 | jobs:
7 |
8 | build:
9 | name: Docs Publish
10 | runs-on: ubuntu-latest
11 | steps:
12 |
13 | - name: Fetch Sources
14 | uses: actions/checkout@v3
15 |
16 | # Documentation publishing
17 |
18 | - name: Set up Python
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: 3.8
22 |
23 | - name: Build mkdocs
24 | run: |
25 | pip3 install -r .github/workflows/mkdocs-requirements.txt
26 | mkdocs build
27 |
28 | - name: Deploy 🚀
29 | if: success()
30 | uses: JamesIves/github-pages-deploy-action@releases/v3
31 | with:
32 | GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
33 | BRANCH: gh-pages # The branch the action should deploy to.
34 | FOLDER: site # The folder the action should deploy.
35 | SINGLE_COMMIT: true
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.rxJavaIntegration
2 |
3 | import cafe.adriel.voyager.rxjava.RxScreenModel
4 | import cafe.adriel.voyager.rxjava.disposables
5 | import cafe.adriel.voyager.sample.sampleItems
6 | import io.reactivex.rxjava3.core.Single
7 | import java.util.concurrent.TimeUnit
8 |
9 | class RxJavaScreenModel : RxScreenModel() {
10 |
11 | sealed class State {
12 | object Loading : State()
13 | data class Result(val items: List) : State()
14 | }
15 |
16 | private val items = sampleItems
17 |
18 | fun getItems() {
19 | Single
20 | .just(items)
21 | .delay(1_000, TimeUnit.MILLISECONDS)
22 | .doOnSubscribe { mutableState.onNext(State.Loading) }
23 | .subscribe { items -> mutableState.onNext(State.Result(items)) }
24 | .let(disposables::add)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/HomeTab.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.tabNavigation.tabs
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Home
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
8 | import cafe.adriel.voyager.navigator.tab.Tab
9 | import cafe.adriel.voyager.navigator.tab.TabOptions
10 |
11 | object HomeTab : Tab {
12 |
13 | override val options: TabOptions
14 | @Composable
15 | get() {
16 | val icon = rememberVectorPainter(Icons.Default.Home)
17 |
18 | return remember {
19 | TabOptions(
20 | index = 0u,
21 | title = "Home",
22 | icon = icon
23 | )
24 | }
25 | }
26 |
27 | @Composable
28 | override fun Content() {
29 | TabContent()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/android-viewmodel/hilt-integration.md:
--------------------------------------------------------------------------------
1 | # Hilt integration
2 |
3 | !!! success
4 | To use the `getViewModel` you should first import `cafe.adriel.voyager:voyager-hilt` (see [Setup](../setup.md)).
5 |
6 | ### @Inject
7 |
8 | Add `@HiltViewModel` and `@Inject` annotations to your `ViewModel`.
9 |
10 | ```kotlin
11 | @HiltViewModel
12 | class HomeViewModel @Inject constructor() : ViewModel() {
13 | // ...
14 | }
15 | ```
16 |
17 | Call `getViewModel()` to get a new instance.
18 |
19 | ```kotlin
20 | class HomeScreen : Screen {
21 |
22 | @Composable
23 | override fun Content() {
24 | val screenModel = getViewModel()
25 | // ...
26 | }
27 | }
28 | ```
29 |
30 | ### @AssistedInject
31 |
32 | Currently there's no Assisted Injection support for Hilt ViewModels ([issue](https://github.com/google/dagger/issues/2287)).
33 |
34 | ### Sample
35 |
36 | !!! info
37 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration).
38 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/ProfileTab.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.tabNavigation.tabs
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Person
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
8 | import cafe.adriel.voyager.navigator.tab.Tab
9 | import cafe.adriel.voyager.navigator.tab.TabOptions
10 |
11 | object ProfileTab : Tab {
12 |
13 | override val options: TabOptions
14 | @Composable
15 | get() {
16 | val icon = rememberVectorPainter(Icons.Default.Person)
17 |
18 | return remember {
19 | TabOptions(
20 | index = 2u,
21 | title = "Profile",
22 | icon = icon
23 | )
24 | }
25 | }
26 |
27 | @Composable
28 | override fun Content() {
29 | TabContent()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorBackHandler.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 | import cafe.adriel.voyager.navigator.Navigator
6 | import cafe.adriel.voyager.navigator.OnBackPressed
7 |
8 | @InternalVoyagerApi
9 | @Composable
10 | public expect fun BackHandler(enabled: Boolean, onBack: () -> Unit)
11 |
12 | @Composable
13 | internal fun NavigatorBackHandler(
14 | navigator: Navigator,
15 | onBackPressed: OnBackPressed
16 | ) {
17 | if (onBackPressed != null) {
18 | BackHandler(
19 | enabled = navigator.canPop || navigator.parent?.canPop ?: false,
20 | onBack = {
21 | if (onBackPressed(navigator.lastItem)) {
22 | if (navigator.pop().not()) {
23 | navigator.parent?.pop()
24 | }
25 | }
26 | }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/androidViewModel/AndroidDetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.androidViewModel
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.screen.Screen
5 | import cafe.adriel.voyager.core.screen.ScreenKey
6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
7 | import cafe.adriel.voyager.navigator.LocalNavigator
8 | import cafe.adriel.voyager.navigator.currentOrThrow
9 | import cafe.adriel.voyager.sample.DetailsContent
10 | import org.koin.androidx.compose.koinViewModel
11 | import org.koin.core.parameter.parametersOf
12 |
13 | data class AndroidDetailsScreen(
14 | val index: Int
15 | ) : Screen {
16 | override val key: ScreenKey = uniqueScreenKey
17 |
18 | @Composable
19 | override fun Content() {
20 | val navigator = LocalNavigator.currentOrThrow
21 | val viewModel = koinViewModel { parametersOf(index) }
22 |
23 | DetailsContent(viewModel, "Item #${viewModel.index}", navigator::pop)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/tabNavigation/tabs/FavoritesTab.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.tabNavigation.tabs
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Favorite
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
8 | import cafe.adriel.voyager.navigator.tab.Tab
9 | import cafe.adriel.voyager.navigator.tab.TabOptions
10 |
11 | object FavoritesTab : Tab {
12 |
13 | override val options: TabOptions
14 | @Composable
15 | get() {
16 | val icon = rememberVectorPainter(Icons.Default.Favorite)
17 |
18 | return remember {
19 | TabOptions(
20 | index = 1u,
21 | title = "Favorites",
22 | icon = icon
23 | )
24 | }
25 | }
26 |
27 | @Composable
28 | override fun Content() {
29 | TabContent()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/annotation/InternalVoyagerApi.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.annotation
2 |
3 | @Target(
4 | allowedTargets = [
5 | AnnotationTarget.CLASS,
6 | AnnotationTarget.PROPERTY,
7 | AnnotationTarget.FUNCTION,
8 | AnnotationTarget.TYPEALIAS,
9 | AnnotationTarget.CONSTRUCTOR
10 | ]
11 | )
12 | @RequiresOptIn(
13 | level = RequiresOptIn.Level.ERROR,
14 | message = "This API is Voyager Internal. It may be changed in the future without notice."
15 | )
16 | public annotation class InternalVoyagerApi
17 |
18 | @Target(
19 | allowedTargets = [
20 | AnnotationTarget.CLASS,
21 | AnnotationTarget.PROPERTY,
22 | AnnotationTarget.FUNCTION,
23 | AnnotationTarget.TYPEALIAS,
24 | AnnotationTarget.CONSTRUCTOR
25 | ]
26 | )
27 | @RequiresOptIn(
28 | level = RequiresOptIn.Level.ERROR,
29 | message = "This API is experimental. It may be changed in the future without notice."
30 | )
31 | public annotation class ExperimentalVoyagerApi
32 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltModule.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import cafe.adriel.voyager.hilt.ScreenModelFactory
5 | import cafe.adriel.voyager.hilt.ScreenModelFactoryKey
6 | import cafe.adriel.voyager.hilt.ScreenModelKey
7 | import dagger.Binds
8 | import dagger.Module
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.android.components.ActivityComponent
11 | import dagger.multibindings.IntoMap
12 |
13 | @Module
14 | @InstallIn(ActivityComponent::class)
15 | abstract class HiltModule {
16 | @Binds
17 | @IntoMap
18 | @ScreenModelKey(HiltListScreenModel::class)
19 | abstract fun bindHiltScreenModel(hiltListScreenModel: HiltListScreenModel): ScreenModel
20 |
21 | @Binds
22 | @IntoMap
23 | @ScreenModelFactoryKey(HiltDetailsScreenModel.Factory::class)
24 | abstract fun bindHiltDetailsScreenModelFactory(
25 | hiltDetailsScreenModelFactory: HiltDetailsScreenModel.Factory
26 | ): ScreenModelFactory
27 | }
28 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.compose.multiplatform)
6 | id("voyager-kmp-module")
7 | }
8 |
9 | android {
10 | namespace = "cafe.adriel.voyager.lifecycle.kmp"
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.voyagerCore)
17 | api(projects.voyagerNavigator)
18 | implementation(compose.runtime)
19 | implementation(compose.runtimeSaveable)
20 | implementation(libs.androidxKmp.lifecycle.viewmodelCompose)
21 | implementation(libs.androidxKmp.lifecycle.viewmodel)
22 | implementation(libs.androidxKmp.lifecycle.runtimeCompose)
23 | implementation(libs.androidxKmp.core.bundle)
24 | }
25 | androidMain.dependencies {
26 | implementation(libs.androidx.lifecycle.savedState)
27 | implementation(compose.ui)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/CrossfadeTransition.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.transitions
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.animation.core.FiniteAnimationSpec
5 | import androidx.compose.animation.core.tween
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import cafe.adriel.voyager.core.screen.Screen
9 | import cafe.adriel.voyager.navigator.Navigator
10 |
11 | @Composable
12 | public fun CrossfadeTransition(
13 | navigator: Navigator,
14 | animationSpec: FiniteAnimationSpec = tween(),
15 | label: String = "Crossfade",
16 | modifier: Modifier = Modifier,
17 | content: @Composable (Screen) -> Unit = { it.Content() }
18 | ) {
19 | Crossfade(
20 | targetState = navigator.lastItem,
21 | animationSpec = animationSpec,
22 | modifier = modifier,
23 | label = label
24 | ) { screen ->
25 | navigator.saveableState("transition", screen) {
26 | content(screen)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/ScreenModelEntryPoint.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | import cafe.adriel.voyager.core.model.ScreenModel
4 | import dagger.hilt.EntryPoint
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.android.components.ActivityComponent
7 | import javax.inject.Provider
8 |
9 | /**
10 | * A Hilt entry point that provide all [ScreenModelFactory] and [ScreenModel] in the graph.
11 | * To have all [ScreenModelFactory] and [ScreenModel] in the graph they must be declared using Multibinding
12 | */
13 | @EntryPoint
14 | @InstallIn(ActivityComponent::class)
15 | public interface ScreenModelEntryPoint {
16 |
17 | /**
18 | * Provide all custom factories declared using multibinding
19 | */
20 | public fun screenModelFactories():
21 | Map, @JvmSuppressWildcards Provider>
22 |
23 | /**
24 | * Provide all screen models declared using multibinding
25 | */
26 | public fun screenModels(): Map, @JvmSuppressWildcards Provider>
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2021 Adriel Café
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/core/lifecycle/ConfigurationChecker.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.NonRestartableComposable
8 | import androidx.compose.runtime.Stable
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.platform.LocalContext
11 |
12 | private tailrec fun Context.getActivity(): Activity? = when (this) {
13 | is Activity -> this
14 | is ContextWrapper -> baseContext.getActivity()
15 | else -> null
16 | }
17 |
18 | @Composable
19 | @NonRestartableComposable
20 | internal actual fun getConfigurationChecker(): ConfigurationChecker {
21 | val context = LocalContext.current
22 | return remember(context) { ConfigurationChecker(context.getActivity()) }
23 | }
24 |
25 | @Stable
26 | internal actual class ConfigurationChecker(private val activity: Activity?) {
27 | actual fun isChangingConfigurations(): Boolean {
28 | return activity?.isChangingConfigurations ?: false
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/voyager-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.multiplatform)
4 | alias(libs.plugins.kotlin.atomicfu)
5 | alias(libs.plugins.kotlin.compose)
6 | alias(libs.plugins.compose.multiplatform)
7 | id("voyager-kmp-module")
8 | }
9 |
10 | android {
11 | namespace = "cafe.adriel.voyager.core"
12 | }
13 |
14 | kotlin {
15 | sourceSets {
16 | commonMain.dependencies {
17 | implementation(compose.runtime)
18 | implementation(compose.runtimeSaveable)
19 | implementation(libs.coroutines.core)
20 | }
21 | commonJvmTest.dependencies {
22 | implementation(libs.junit.api)
23 | runtimeOnly(libs.junit.engine)
24 | }
25 | androidMain.dependencies {
26 | implementation(libs.androidx.activity.compose)
27 | implementation(libs.androidx.lifecycle.runtime)
28 | implementation(libs.androidx.lifecycle.savedState)
29 | implementation(libs.androidx.lifecycle.viewModelKtx)
30 | implementation(libs.androidx.lifecycle.viewModelCompose)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dropbox focus plugin
2 | *.focus
3 |
4 | # https://github.com/github/gitignore/blob/main/Android.gitignore
5 | # Gradle files
6 | .gradle/
7 | build/
8 |
9 | # Local configuration file (sdk path, etc)
10 | local.properties
11 |
12 | # Log/OS Files
13 | *.log
14 |
15 | # Android Studio generated files and folders
16 | captures/
17 | .externalNativeBuild/
18 | .cxx/
19 | *.apk
20 | output.json
21 |
22 | # IntelliJ
23 | *.iml
24 | .idea/
25 | misc.xml
26 | deploymentTargetDropDown.xml
27 | render.experimental.xml
28 |
29 | # Keystore files
30 | *.jks
31 | *.keystore
32 |
33 | # Google Services (e.g. APIs or Firebase)
34 | google-services.json
35 |
36 | # Android Profiling
37 | *.hprof
38 |
39 | # https://github.com/github/gitignore/blob/main/Kotlin.gitignore
40 | # Kotlin data directory
41 | .kotlin/
42 |
43 | # Compiled class file
44 | *.class
45 |
46 | # BlueJ files
47 | *.ctxt
48 |
49 | # Mobile Tools for Java (J2ME)
50 | .mtj.tmp/
51 |
52 | # Package Files #
53 | *.jar
54 | *.war
55 | *.nar
56 | *.ear
57 | *.zip
58 | *.tar.gz
59 | *.rar
60 |
61 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
62 | hs_err_pid*
63 | replay_pid*
64 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/NavigatorLifecycleKMPOwner.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
7 | import cafe.adriel.voyager.navigator.Navigator
8 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorDisposable
9 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore
10 |
11 | @InternalVoyagerApi
12 | @ExperimentalVoyagerApi
13 | @Composable
14 | public fun Navigator.rememberNavigatorLifecycleOwner(): VoyagerLifecycleKMPOwner {
15 | return remember(this) {
16 | NavigatorLifecycleStore.get(this) {
17 | NavigatorLifecycleKMPOwner()
18 | }.owner
19 | }
20 | }
21 |
22 | @InternalVoyagerApi
23 | @ExperimentalVoyagerApi
24 | public class NavigatorLifecycleKMPOwner : NavigatorDisposable {
25 | public val owner: VoyagerLifecycleKMPOwner = VoyagerLifecycleKMPOwner()
26 |
27 | override fun onDispose(navigator: Navigator) {
28 | owner.onDispose()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration/KodeinScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.kodeinIntegration
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.Composable
6 | import cafe.adriel.voyager.core.screen.Screen
7 | import cafe.adriel.voyager.core.screen.ScreenKey
8 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
9 | import cafe.adriel.voyager.kodein.ScreenContext
10 | import cafe.adriel.voyager.kodein.rememberScreenModel
11 | import cafe.adriel.voyager.sample.ListContent
12 | import org.kodein.di.compose.localDI
13 | import org.kodein.di.instance
14 | import org.kodein.di.on
15 |
16 | class KodeinScreen : Screen {
17 |
18 | override val key: ScreenKey = uniqueScreenKey
19 |
20 | @Composable
21 | override fun Content() {
22 | val screenModel = rememberScreenModel()
23 | val scopedDependency by localDI().on(ScreenContext(this)).instance()
24 |
25 | Column {
26 | Text(scopedDependency.toString())
27 | ListContent(screenModel.items)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | public interface ScreenLifecycleOwner : ScreenLifecycleContentProvider, ScreenDisposable
8 |
9 | public interface ScreenLifecycleContentProvider {
10 | /**
11 | * Called before rendering the Screen Content.
12 | *
13 | * IMPORTANT: This is only called when ScreenLifecycleOwner is provided by [ScreenLifecycleProvider] or [NavigatorScreenLifecycleProvider].
14 | */
15 | @Composable
16 | public fun ProvideBeforeScreenContent(
17 | provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit,
18 | content: @Composable () -> Unit
19 | ): Unit = content()
20 | }
21 |
22 | public interface ScreenDisposable {
23 | /**
24 | * Called on the Screen leaves the stack.
25 | */
26 | public fun onDispose(screen: Screen) {}
27 | }
28 |
29 | @InternalVoyagerApi
30 | public object DefaultScreenLifecycleOwner : ScreenLifecycleOwner
31 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/registry/ScreenRegistry.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.registry
2 |
3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap
4 | import cafe.adriel.voyager.core.platform.multiplatformName
5 | import cafe.adriel.voyager.core.screen.Screen
6 | import kotlin.reflect.KClass
7 |
8 | private typealias ProviderKey = KClass
9 |
10 | private typealias ScreenFactory = (ScreenProvider) -> Screen
11 |
12 | public object ScreenRegistry {
13 |
14 | @PublishedApi
15 | internal val factories: ThreadSafeMap = ThreadSafeMap()
16 |
17 | public operator fun invoke(block: ScreenRegistry.() -> Unit) {
18 | this.block()
19 | }
20 |
21 | public inline fun register(noinline factory: (T) -> Screen) {
22 | @Suppress("UNCHECKED_CAST")
23 | factories[T::class] = factory as ScreenFactory
24 | }
25 |
26 | public fun get(provider: ScreenProvider): Screen {
27 | val factory = factories[provider::class]
28 | ?: error("ScreenProvider not registered: ${provider::class.multiplatformName}")
29 | return factory(provider)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/stack/Stack.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.stack
2 |
3 | public inline fun Stack- .popUntil(): Boolean =
4 | popUntil { item -> item is I }
5 |
6 | public enum class StackEvent {
7 | Push,
8 | Replace,
9 | Pop,
10 | Idle
11 | }
12 |
13 | public interface Stack
- {
14 |
15 | public val items: List
-
16 |
17 | public val lastEvent: StackEvent
18 |
19 | public val lastItemOrNull: Item?
20 |
21 | public val size: Int
22 |
23 | public val isEmpty: Boolean
24 |
25 | public val canPop: Boolean
26 |
27 | public infix fun push(item: Item)
28 |
29 | public infix fun push(items: List
- )
30 |
31 | public infix fun replace(item: Item)
32 |
33 | public infix fun replaceAll(item: Item)
34 |
35 | public infix fun replaceAll(items: List
- )
36 |
37 | public fun pop(): Boolean
38 |
39 | public fun popAll()
40 |
41 | public infix fun popUntil(predicate: (Item) -> Boolean): Boolean
42 |
43 | public operator fun plusAssign(item: Item)
44 |
45 | public operator fun plusAssign(items: List
- )
46 |
47 | public fun clearEvent()
48 | }
49 |
--------------------------------------------------------------------------------
/docs/screenmodel/kodein-integration.md:
--------------------------------------------------------------------------------
1 | # Kodein integration
2 |
3 | !!! success
4 | To use the `rememberScreenModel` you should first import `cafe.adriel.voyager:voyager-kodein` (see [Setup](../setup.md)).
5 |
6 | Declare your `ScreenModel`s using the `bindProvider` bind.
7 |
8 | ```kotlin
9 | val homeModule = DI.Module(name = "home") {
10 | bindProvider { HomeScreenModel() }
11 | }
12 | ```
13 |
14 | Call `rememberScreenModel()` to get a new instance.
15 |
16 | ```kotlin
17 | class HomeScreen : Screen {
18 |
19 | @Composable
20 | override fun Content() {
21 | val screenModel = rememberScreenModel()
22 | // ...
23 | }
24 | }
25 | ```
26 |
27 | ### Sample
28 |
29 | !!! info
30 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/kodeinIntegration).
31 |
32 | ### Navigator scoped ScreenModel
33 |
34 | ```kotlin
35 | class HomeScreen : Screen {
36 |
37 | @Composable
38 | override fun Content() {
39 | val navigator = LocalNavigator.currentOrThrow
40 | val screenModel = navigator.rememberNavigatorScreenModel()
41 | // ...
42 | }
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/LifecycleProvider.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import cafe.adriel.voyager.core.lifecycle.LocalNavigatorScreenLifecycleProvider
6 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider
7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider
8 | import cafe.adriel.voyager.core.screen.Screen
9 |
10 | internal val defaultNavigatorScreenLifecycleProvider = DefaultNavigatorScreenLifecycleProvider()
11 |
12 | internal expect class DefaultNavigatorScreenLifecycleProvider() : NavigatorScreenLifecycleProvider {
13 | override fun provide(screen: Screen): List
14 | }
15 |
16 | @Composable
17 | internal fun getNavigatorScreenLifecycleProvider(screen: Screen): List {
18 | val navigatorScreenLifecycleProvider = LocalNavigatorScreenLifecycleProvider.current
19 | ?: defaultNavigatorScreenLifecycleProvider
20 |
21 | return remember(screen.key) {
22 | navigatorScreenLifecycleProvider.provide(screen)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/bottomSheetNavigation/BackScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.bottomSheetNavigation
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.material.Button
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import cafe.adriel.voyager.core.screen.Screen
11 | import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
12 | import cafe.adriel.voyager.sample.basicNavigation.BasicNavigationScreen
13 |
14 | class BackScreen : Screen {
15 |
16 | @Composable
17 | override fun Content() {
18 | val bottomSheetNavigator = LocalBottomSheetNavigator.current
19 |
20 | Box(
21 | contentAlignment = Alignment.Center,
22 | modifier = Modifier.fillMaxSize()
23 | ) {
24 | Button(
25 | onClick = { bottomSheetNavigator.show(BasicNavigationScreen(index = 0, wrapContent = true)) }
26 | ) {
27 | Text(text = "Show BottomSheet")
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/LifecycleEffectStore.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.lifecycle
2 |
3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap
4 | import cafe.adriel.voyager.core.concurrent.ThreadSafeSet
5 | import cafe.adriel.voyager.core.screen.Screen
6 | import cafe.adriel.voyager.core.screen.ScreenKey
7 |
8 | internal object LifecycleEffectStore : ScreenDisposable {
9 | private val executedLifecycles = ThreadSafeMap>()
10 |
11 | fun store(screen: Screen, effectKey: String): LifecycleEffectOnceScope {
12 | val set = executedLifecycles.getOrPut(screen.key) { ThreadSafeSet() }
13 | val scope = LifecycleEffectOnceScope(uniqueKey = effectKey, set.size + 1)
14 | set.add(scope)
15 |
16 | return scope
17 | }
18 |
19 | fun hasExecuted(screen: Screen, effectKey: String): Boolean =
20 | executedLifecycles.get(screen.key)?.any { it.uniqueKey == effectKey } == true
21 |
22 | override fun onDispose(screen: Screen) {
23 | val scopes = executedLifecycles.remove(screen.key)
24 | scopes?.sortedBy { it.registerOrderIndex }?.reversed()?.forEach { scope ->
25 | scope.onDisposed?.invoke()
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/voyager-kodein/api/android/voyager-kodein.api:
--------------------------------------------------------------------------------
1 | public final class cafe/adriel/voyager/kodein/ScreenContext {
2 | public static final field $stable I
3 | public fun (Lcafe/adriel/voyager/core/screen/Screen;)V
4 | public fun equals (Ljava/lang/Object;)Z
5 | public final fun getScreen ()Lcafe/adriel/voyager/core/screen/Screen;
6 | public fun hashCode ()I
7 | }
8 |
9 | public class cafe/adriel/voyager/kodein/ScreenLifecycleScope : org/kodein/di/bindings/Scope {
10 | public static final field $stable I
11 | public static final field multiItem Lcafe/adriel/voyager/kodein/ScreenLifecycleScope$multiItem;
12 | public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
13 | public fun getRegistry (Lcafe/adriel/voyager/kodein/ScreenContext;)Lorg/kodein/di/bindings/ScopeRegistry;
14 | public synthetic fun getRegistry (Ljava/lang/Object;)Lorg/kodein/di/bindings/ScopeRegistry;
15 | }
16 |
17 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScope$multiItem : cafe/adriel/voyager/kodein/ScreenLifecycleScope {
18 | }
19 |
20 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScopeKt {
21 | public static final fun rememberScreenContext (Landroidx/compose/runtime/Composer;I)Lcafe/adriel/voyager/kodein/ScreenContext;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/voyager-kodein/api/desktop/voyager-kodein.api:
--------------------------------------------------------------------------------
1 | public final class cafe/adriel/voyager/kodein/ScreenContext {
2 | public static final field $stable I
3 | public fun (Lcafe/adriel/voyager/core/screen/Screen;)V
4 | public fun equals (Ljava/lang/Object;)Z
5 | public final fun getScreen ()Lcafe/adriel/voyager/core/screen/Screen;
6 | public fun hashCode ()I
7 | }
8 |
9 | public class cafe/adriel/voyager/kodein/ScreenLifecycleScope : org/kodein/di/bindings/Scope {
10 | public static final field $stable I
11 | public static final field multiItem Lcafe/adriel/voyager/kodein/ScreenLifecycleScope$multiItem;
12 | public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
13 | public fun getRegistry (Lcafe/adriel/voyager/kodein/ScreenContext;)Lorg/kodein/di/bindings/ScopeRegistry;
14 | public synthetic fun getRegistry (Ljava/lang/Object;)Lorg/kodein/di/bindings/ScopeRegistry;
15 | }
16 |
17 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScope$multiItem : cafe/adriel/voyager/kodein/ScreenLifecycleScope {
18 | }
19 |
20 | public final class cafe/adriel/voyager/kodein/ScreenLifecycleScopeKt {
21 | public static final fun rememberScreenContext (Landroidx/compose/runtime/Composer;I)Lcafe/adriel/voyager/kodein/ScreenContext;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/docs/android-viewmodel/viewmodel-kmp.md:
--------------------------------------------------------------------------------
1 | # ViewModel KMP
2 |
3 | Since 1.1.0-beta01 we have introduce a experimental API for ViewModel KMP. It is under the package `cafe.adriel.voyager:voyager-lifecycle-kmp` (see [Setup](../setup.md)).
4 |
5 | You will need to call `ProvideNavigatorLifecycleKMPSupport` before all `Navigator` calls and it will be working out of the box.
6 |
7 | ```kotlin
8 | @Composable
9 | fun MainView() {
10 | ProvideNavigatorLifecycleKMPSupport {
11 | Navigator(...)
12 | }
13 | }
14 |
15 | class MyScreen : Screen {
16 | @Composable
17 | fun Content() {
18 | val myViewModel = viewModel { MyScreenViewModel() }
19 | }
20 | }
21 | ```
22 |
23 | ## Navigator scoped ViewModel
24 |
25 | Voyager 1.1.0-beta01 also have introduced the support for Navigator scoped ViewModel and Lifecycle.
26 | This will make easy to share a ViewModel cross screen of the same navigator.
27 |
28 | ```kotlin
29 | class MyScreen : Screen {
30 | @Composable
31 | fun Content() {
32 | val myViewModel = navigatorViewModel { MyScreenViewModel() }
33 | }
34 | }
35 | ```
36 |
37 | ## Lifecycle KMP
38 |
39 | This version also brings the Lifecycle events for Screen lifecycle in KMP, now is possible to
40 | a generic third party API that listen to Lifecycle of a Screen in KMP.
41 |
--------------------------------------------------------------------------------
/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/concurrent/ThreadSafeList.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | public expect class ThreadSafeList() : MutableList {
4 | override fun add(element: T): Boolean
5 | override fun add(index: Int, element: T)
6 | override fun addAll(elements: Collection): Boolean
7 | override fun addAll(index: Int, elements: Collection): Boolean
8 | override fun clear()
9 | override fun listIterator(): MutableListIterator
10 | override fun listIterator(index: Int): MutableListIterator
11 | override fun remove(element: T): Boolean
12 | override fun removeAll(elements: Collection): Boolean
13 | override fun removeAt(index: Int): T
14 | override fun retainAll(elements: Collection): Boolean
15 | override fun set(index: Int, element: T): T
16 | override fun subList(fromIndex: Int, toIndex: Int): MutableList
17 | override fun contains(element: T): Boolean
18 | override fun containsAll(elements: Collection): Boolean
19 | override fun get(index: Int): T
20 | override fun indexOf(element: T): Int
21 | override fun isEmpty(): Boolean
22 | override fun iterator(): MutableIterator
23 | override fun lastIndexOf(element: T): Int
24 | override val size: Int
25 | }
26 |
--------------------------------------------------------------------------------
/docs/navigation/nested-navigation.md:
--------------------------------------------------------------------------------
1 | # Nested navigation
2 |
3 | ### Nested Navigators
4 |
5 | Going a little further, it's possible to have nested navigators. The `Navigator` has a `level` property (so you can check how deeper your are) and can have a `parent` navigator (if you need to interact with it).
6 |
7 | ```kotlin
8 | setContent {
9 | Navigator(ScreenA) { navigator0 ->
10 | println(navigator.level)
11 | // 0
12 | println(navigator.parent == null)
13 | // true
14 | Navigator(ScreenB) { navigator1 ->
15 | println(navigator.level)
16 | // 1
17 | println(navigator.parent == navigator0)
18 | // true
19 | Navigator(ScreenC) { navigator2 ->
20 | println(navigator.level)
21 | // 2
22 | println(navigator.parent == navigator1)
23 | // true
24 | }
25 | }
26 | }
27 | }
28 | ```
29 |
30 | Another operation is the `popUntilRoot()`, it will recursively pop all screens starting from the leaf navigator until the root one.
31 |
32 | ### Sample
33 |
34 | 
35 |
36 | !!! info
37 | Source code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/nestedNavigation).
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/ScreenLifecycleJetpackOwner.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner
8 | import cafe.adriel.voyager.core.screen.Screen
9 |
10 | @InternalVoyagerApi
11 | @ExperimentalVoyagerApi
12 | public class ScreenLifecycleKMPOwner : ScreenLifecycleOwner {
13 | public val owner: VoyagerLifecycleKMPOwner = VoyagerLifecycleKMPOwner()
14 |
15 | @Composable
16 | override fun ProvideBeforeScreenContent(
17 | provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit,
18 | content: @Composable () -> Unit
19 | ) {
20 | provideSaveableState("lifecycle") {
21 | owner.LifecycleDisposableEffect()
22 |
23 | val hooks = owner.getHooks()
24 |
25 | CompositionLocalProvider(*hooks.toTypedArray()) {
26 | content()
27 | }
28 | }
29 | }
30 |
31 | override fun onDispose(screen: Screen) {
32 | owner.onDispose()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/voyager-screenmodel/src/commonMain/kotlin/cafe/adriel/voyager/core/model/NavigatorScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.model
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisallowComposableCalls
5 | import androidx.compose.runtime.remember
6 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
7 | import cafe.adriel.voyager.navigator.Navigator
8 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorDisposable
9 | import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore
10 |
11 | @Composable
12 | public inline fun Navigator.rememberNavigatorScreenModel(
13 | tag: String? = null,
14 | crossinline factory: @DisallowComposableCalls () -> T
15 | ): T {
16 | // register the navigator lifecycle listener if is not already registered
17 | remember(this) {
18 | NavigatorLifecycleStore.get(this) { NavigatorScreenModelDisposer }
19 | }
20 |
21 | return remember(ScreenModelStore.getKey(this.key, tag)) {
22 | ScreenModelStore.getOrPut(this.key, tag, factory)
23 | }
24 | }
25 |
26 | @InternalVoyagerApi
27 | public object NavigatorScreenModelDisposer : NavigatorDisposable {
28 | override fun onDispose(navigator: Navigator) {
29 | ScreenModelStore.onDisposeNavigator(navigator.key)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.dropbox.focus.FocusExtension
2 |
3 | rootProject.name = "voyager"
4 |
5 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
6 |
7 | pluginManagement {
8 | repositories {
9 | mavenCentral()
10 | google()
11 | gradlePluginPortal()
12 | }
13 | resolutionStrategy {
14 | eachPlugin {
15 | if (requested.version == null) return@eachPlugin
16 | when (requested.id.id) {
17 | "com.android.tools.build" -> {
18 | useModule("${requested.id.id}:gradle:${requested.version}")
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
25 | dependencyResolutionManagement {
26 | // repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
27 | repositories {
28 | google()
29 | mavenCentral()
30 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
31 | }
32 | versionCatalogs {
33 | create("samplesCatalog") {
34 | from(files("./gradle/samples.versions.toml"))
35 | }
36 | }
37 | }
38 |
39 | includeBuild("build-config")
40 |
41 | plugins {
42 | id("com.dropbox.focus") version "0.4.0"
43 | }
44 |
45 | configure {
46 | allSettingsFileName.set("includes.gradle.kts")
47 | focusFileName.set(".focus")
48 | }
49 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration/LiveDataScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.liveDataIntegration
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.currentCompositeKeyHash
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.livedata.observeAsState
8 | import cafe.adriel.voyager.core.model.rememberScreenModel
9 | import cafe.adriel.voyager.core.screen.Screen
10 | import cafe.adriel.voyager.core.screen.ScreenKey
11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
12 | import cafe.adriel.voyager.sample.ListContent
13 | import cafe.adriel.voyager.sample.LoadingContent
14 |
15 | class LiveDataScreen : Screen {
16 |
17 | override val key: ScreenKey = uniqueScreenKey
18 |
19 | @Composable
20 | override fun Content() {
21 | val screenModel = rememberScreenModel { LiveDataScreenModel() }
22 | val state by screenModel.state.observeAsState()
23 |
24 | when (val result = state) {
25 | is LiveDataScreenModel.State.Loading -> LoadingContent()
26 | is LiveDataScreenModel.State.Result -> ListContent(result.items)
27 | else -> Unit
28 | }
29 |
30 | LaunchedEffect(currentCompositeKeyHash) {
31 | screenModel.getItems()
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/rxjavaIntegration/RxJavaScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.rxJavaIntegration
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.currentCompositeKeyHash
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.rxjava3.subscribeAsState
8 | import cafe.adriel.voyager.core.model.rememberScreenModel
9 | import cafe.adriel.voyager.core.screen.Screen
10 | import cafe.adriel.voyager.core.screen.ScreenKey
11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
12 | import cafe.adriel.voyager.sample.ListContent
13 | import cafe.adriel.voyager.sample.LoadingContent
14 |
15 | class RxJavaScreen : Screen {
16 |
17 | override val key: ScreenKey = uniqueScreenKey
18 |
19 | @Composable
20 | override fun Content() {
21 | val screenModel = rememberScreenModel { RxJavaScreenModel() }
22 | val state by screenModel.state.subscribeAsState(initial = RxJavaScreenModel.State.Loading)
23 |
24 | when (val result = state) {
25 | is RxJavaScreenModel.State.Loading -> LoadingContent()
26 | is RxJavaScreenModel.State.Result -> ListContent(result.items)
27 | }
28 |
29 | LaunchedEffect(currentCompositeKeyHash) {
30 | screenModel.getItems()
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | release:
4 | types: [prereleased, released]
5 |
6 | jobs:
7 |
8 | release:
9 | name: Publish Voyager
10 | runs-on: macOS-latest
11 | timeout-minutes: 60
12 | steps:
13 |
14 | - name: Fetch Sources
15 | uses: actions/checkout@v3
16 | with:
17 | ref: ${{ github.event.release.tag_name }}
18 |
19 | - name: Copy CI gradle.properties
20 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
21 |
22 | - name: Setup Java
23 | uses: actions/setup-java@v3
24 | with:
25 | java-version: 17
26 | distribution: 'temurin'
27 |
28 | - name: Api Check
29 | run: |
30 | ./gradlew apiCheck --stacktrace
31 |
32 | - name: Deploy to Sonatype
33 | run: |
34 | NEW_VERSION=$(echo "${GITHUB_REF}" | cut -d "/" -f3)
35 | echo "New version: ${NEW_VERSION}"
36 | ./gradlew -Pversion=${NEW_VERSION} publish --stacktrace
37 | env:
38 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
39 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
40 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
41 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/LifecycleProvider.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.runtime.remember
6 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
7 | import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
8 | import cafe.adriel.voyager.core.lifecycle.LocalNavigatorScreenLifecycleProvider
9 | import cafe.adriel.voyager.core.lifecycle.NavigatorScreenLifecycleProvider
10 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleContentProvider
11 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore
12 | import cafe.adriel.voyager.core.screen.Screen
13 |
14 | @ExperimentalVoyagerApi
15 | @Composable
16 | public fun ProvideNavigatorLifecycleKMPSupport(
17 | content: @Composable () -> Unit
18 | ) {
19 | val provider = remember { JetpackSupportProvider() }
20 | CompositionLocalProvider(
21 | LocalNavigatorScreenLifecycleProvider provides provider
22 | ) {
23 | content()
24 | }
25 | }
26 |
27 | @InternalVoyagerApi
28 | @ExperimentalVoyagerApi
29 | public class JetpackSupportProvider : NavigatorScreenLifecycleProvider {
30 | override fun provide(screen: Screen): List =
31 | listOf(ScreenLifecycleStore.get(screen) { ScreenLifecycleKMPOwner() })
32 | }
33 |
--------------------------------------------------------------------------------
/docs/screenmodel/koin-integration.md:
--------------------------------------------------------------------------------
1 | # Koin integration
2 |
3 | !!! success
4 | To use the `getScreenModel` you should first import `cafe.adriel.voyager:voyager-koin` (see [Setup](../setup.md)).
5 |
6 | !!! warning
7 | Since [1.1.0-alpha04](https://github.com/adrielcafe/voyager/releases/tag/1.1.0-alpha04) we have rename the `getScreenModel` to `koinScreenModel`, this is a change to follow Koin Compose naming schema. The previous `getScreenModel` is deprecated and will be removed on 1.1.0
8 |
9 | Declare your `ScreenModel`s using the `factory` component.
10 |
11 | ```kotlin
12 | val homeModule = module {
13 | factory { HomeScreenModel() }
14 | }
15 | ```
16 |
17 | Call `getScreenModel()` to get a new instance.
18 |
19 | ```kotlin
20 | class HomeScreen : Screen {
21 |
22 | @Composable
23 | override fun Content() {
24 | val screenModel = getScreenModel()
25 | // ...
26 | }
27 | }
28 | ```
29 |
30 | ### Sample
31 |
32 | !!! info
33 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/koinIntegration).
34 |
35 | ### Navigator scoped ScreenModel
36 |
37 | ```kotlin
38 | class HomeScreen : Screen {
39 |
40 | @Composable
41 | override fun Content() {
42 | val navigator = LocalNavigator.currentOrThrow
43 | val screenModel = navigator.getNavigatorScreenModel()
44 | // ...
45 | }
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorSaverInternal.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.ProvidableCompositionLocal
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.runtime.saveable.SaveableStateHolder
7 | import androidx.compose.runtime.saveable.rememberSaveable
8 | import androidx.compose.runtime.staticCompositionLocalOf
9 | import cafe.adriel.voyager.core.screen.Screen
10 | import cafe.adriel.voyager.navigator.LocalNavigatorSaver
11 | import cafe.adriel.voyager.navigator.Navigator
12 | import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
13 |
14 | internal val LocalNavigatorStateHolder: ProvidableCompositionLocal =
15 | staticCompositionLocalOf { error("LocalNavigatorStateHolder not initialized") }
16 |
17 | @Composable
18 | internal fun rememberNavigator(
19 | screens: List,
20 | key: String,
21 | disposeBehavior: NavigatorDisposeBehavior,
22 | parent: Navigator?
23 | ): Navigator {
24 | val stateHolder = LocalNavigatorStateHolder.current
25 | val navigatorSaver = LocalNavigatorSaver.current
26 | val saver = remember(navigatorSaver, stateHolder, parent, disposeBehavior) {
27 | navigatorSaver.saver(screens, key, stateHolder, disposeBehavior, parent)
28 | }
29 |
30 | return rememberSaveable(saver = saver, key = key) {
31 | Navigator(screens, key, stateHolder, disposeBehavior, parent)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/screenModel/DetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.screenModel
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.collectAsState
6 | import androidx.compose.runtime.currentCompositeKeyHash
7 | import androidx.compose.runtime.getValue
8 | import cafe.adriel.voyager.core.model.rememberScreenModel
9 | import cafe.adriel.voyager.core.screen.Screen
10 | import cafe.adriel.voyager.core.screen.ScreenKey
11 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
12 | import cafe.adriel.voyager.navigator.LocalNavigator
13 | import cafe.adriel.voyager.navigator.currentOrThrow
14 | import cafe.adriel.voyager.sample.DetailsContent
15 | import cafe.adriel.voyager.sample.LoadingContent
16 |
17 | data class DetailsScreen(
18 | val index: Int
19 | ) : Screen {
20 |
21 | override val key: ScreenKey = uniqueScreenKey
22 |
23 | @Composable
24 | override fun Content() {
25 | val navigator = LocalNavigator.currentOrThrow
26 | val screenModel = rememberScreenModel { DetailsScreenModel(index) }
27 | val state by screenModel.state.collectAsState()
28 |
29 | when (val result = state) {
30 | is DetailsScreenModel.State.Loading -> LoadingContent()
31 | is DetailsScreenModel.State.Result -> DetailsContent(screenModel, result.item, navigator::pop)
32 | }
33 |
34 | LaunchedEffect(currentCompositeKeyHash) {
35 | screenModel.getItem(index)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMutableIterator.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.locks.SynchronizedObject
4 | import kotlinx.atomicfu.locks.synchronized
5 |
6 | internal open class ThreadSafeMutableIterator(
7 | private val syncObject: SynchronizedObject,
8 | private val delegate: MutableIterator
9 | ) : MutableIterator {
10 | override fun hasNext(): Boolean = synchronized(syncObject) { delegate.hasNext() }
11 |
12 | override fun next(): E = synchronized(syncObject) { delegate.next() }
13 |
14 | override fun remove() {
15 | synchronized(syncObject) { delegate.remove() }
16 | }
17 | }
18 |
19 | internal class ThreadSafeMutableListIterator(
20 | private val syncObject: SynchronizedObject,
21 | private val delegate: MutableListIterator
22 | ) : ThreadSafeMutableIterator(syncObject, delegate),
23 | MutableListIterator {
24 |
25 | override fun hasPrevious(): Boolean = synchronized(syncObject) { delegate.hasPrevious() }
26 |
27 | override fun nextIndex(): Int = synchronized(syncObject) { delegate.nextIndex() }
28 |
29 | override fun previous(): E = synchronized(syncObject) { delegate.previous() }
30 |
31 | override fun previousIndex(): Int = synchronized(syncObject) { delegate.previousIndex() }
32 |
33 | override fun add(element: E) {
34 | synchronized(syncObject) { delegate.add(element) }
35 | }
36 |
37 | override fun set(element: E) {
38 | synchronized(syncObject) { delegate.set(element) }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/voyager-screenmodel/api/android/voyager-screenmodel.api:
--------------------------------------------------------------------------------
1 | public abstract interface class cafe/adriel/voyager/core/model/ScreenModel {
2 | public fun onDispose ()V
3 | }
4 |
5 | public final class cafe/adriel/voyager/core/model/ScreenModel$DefaultImpls {
6 | public static fun onDispose (Lcafe/adriel/voyager/core/model/ScreenModel;)V
7 | }
8 |
9 | public final class cafe/adriel/voyager/core/model/ScreenModelKt {
10 | public static final fun getScreenModelScope (Lcafe/adriel/voyager/core/model/ScreenModel;)Lkotlinx/coroutines/CoroutineScope;
11 | }
12 |
13 | public final class cafe/adriel/voyager/core/model/ScreenModelStore : cafe/adriel/voyager/core/lifecycle/ScreenDisposable {
14 | public static final field $stable I
15 | public static final field INSTANCE Lcafe/adriel/voyager/core/model/ScreenModelStore;
16 | public final fun getDependencies ()Ljava/util/Map;
17 | public final fun getDependencyKey (Lcafe/adriel/voyager/core/model/ScreenModel;Ljava/lang/String;)Ljava/lang/String;
18 | public final fun getLastScreenModelKey ()Lkotlinx/coroutines/flow/MutableStateFlow;
19 | public final fun getScreenModels ()Ljava/util/Map;
20 | public fun onDispose (Lcafe/adriel/voyager/core/screen/Screen;)V
21 | }
22 |
23 | public abstract class cafe/adriel/voyager/core/model/StateScreenModel : cafe/adriel/voyager/core/model/ScreenModel {
24 | public static final field $stable I
25 | public fun (Ljava/lang/Object;)V
26 | protected final fun getMutableState ()Lkotlinx/coroutines/flow/MutableStateFlow;
27 | public final fun getState ()Lkotlinx/coroutines/flow/StateFlow;
28 | public fun onDispose ()V
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/voyager-screenmodel/api/desktop/voyager-screenmodel.api:
--------------------------------------------------------------------------------
1 | public abstract interface class cafe/adriel/voyager/core/model/ScreenModel {
2 | public fun onDispose ()V
3 | }
4 |
5 | public final class cafe/adriel/voyager/core/model/ScreenModel$DefaultImpls {
6 | public static fun onDispose (Lcafe/adriel/voyager/core/model/ScreenModel;)V
7 | }
8 |
9 | public final class cafe/adriel/voyager/core/model/ScreenModelKt {
10 | public static final fun getScreenModelScope (Lcafe/adriel/voyager/core/model/ScreenModel;)Lkotlinx/coroutines/CoroutineScope;
11 | }
12 |
13 | public final class cafe/adriel/voyager/core/model/ScreenModelStore : cafe/adriel/voyager/core/lifecycle/ScreenDisposable {
14 | public static final field $stable I
15 | public static final field INSTANCE Lcafe/adriel/voyager/core/model/ScreenModelStore;
16 | public final fun getDependencies ()Ljava/util/Map;
17 | public final fun getDependencyKey (Lcafe/adriel/voyager/core/model/ScreenModel;Ljava/lang/String;)Ljava/lang/String;
18 | public final fun getLastScreenModelKey ()Lkotlinx/coroutines/flow/MutableStateFlow;
19 | public final fun getScreenModels ()Ljava/util/Map;
20 | public fun onDispose (Lcafe/adriel/voyager/core/screen/Screen;)V
21 | }
22 |
23 | public abstract class cafe/adriel/voyager/core/model/StateScreenModel : cafe/adriel/voyager/core/model/ScreenModel {
24 | public static final field $stable I
25 | public fun (Ljava/lang/Object;)V
26 | protected final fun getMutableState ()Lkotlinx/coroutines/flow/MutableStateFlow;
27 | public final fun getState ()Lkotlinx/coroutines/flow/StateFlow;
28 | public fun onDispose ()V
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.kodein
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.model.ScreenModel
5 | import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
6 | import cafe.adriel.voyager.core.model.rememberScreenModel
7 | import cafe.adriel.voyager.core.screen.Screen
8 | import cafe.adriel.voyager.navigator.Navigator
9 | import org.kodein.di.compose.localDI
10 | import org.kodein.di.direct
11 | import org.kodein.di.provider
12 |
13 | @Composable
14 | public inline fun Screen.rememberScreenModel(
15 | tag: Any? = null
16 | ): T = with(localDI()) {
17 | rememberScreenModel(tag = tag?.toString()) { direct.provider(tag)() }
18 | }
19 |
20 | @Composable
21 | public inline fun Screen.rememberScreenModel(
22 | tag: Any? = null,
23 | arg: A
24 | ): T = with(localDI()) {
25 | rememberScreenModel(tag = tag?.toString()) { direct.provider(tag, arg)() }
26 | }
27 |
28 | @Composable
29 | public inline fun Navigator.rememberNavigatorScreenModel(
30 | tag: Any? = null
31 | ): T = with(localDI()) {
32 | rememberNavigatorScreenModel(tag = tag?.toString()) { direct.provider(tag)() }
33 | }
34 |
35 | @Composable
36 | public inline fun Navigator.rememberNavigatorScreenModel(
37 | tag: Any? = null,
38 | arg: A
39 | ): T = with(localDI()) {
40 | rememberNavigatorScreenModel(tag = tag?.toString()) { direct.provider(tag, arg)() }
41 | }
42 |
--------------------------------------------------------------------------------
/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/lifecycle/NavigatorLifecycleStore.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator.lifecycle
2 |
3 | import cafe.adriel.voyager.core.concurrent.ThreadSafeMap
4 | import cafe.adriel.voyager.navigator.Navigator
5 | import kotlin.reflect.KType
6 | import kotlin.reflect.typeOf
7 |
8 | public typealias NavigatorKey = String
9 |
10 | public object NavigatorLifecycleStore {
11 |
12 | private val owners = ThreadSafeMap>()
13 |
14 | /**
15 | * Register a NavigatorDisposable that will be called `onDispose` on the
16 | * [navigator] leaves the Composition.
17 | */
18 | public inline fun get(
19 | navigator: Navigator,
20 | noinline factory: (NavigatorKey) -> T
21 | ): T {
22 | return get(navigator, typeOf(), factory) as T
23 | }
24 |
25 | @PublishedApi
26 | internal fun get(
27 | navigator: Navigator,
28 | screenDisposeListenerType: KType,
29 | factory: (NavigatorKey) -> T
30 | ): NavigatorDisposable {
31 | return owners.getOrPut(navigator.key) {
32 | ThreadSafeMap().apply {
33 | put(screenDisposeListenerType, factory(navigator.key))
34 | }
35 | }.getOrPut(screenDisposeListenerType) {
36 | factory(navigator.key)
37 | }
38 | }
39 |
40 | public fun remove(navigator: Navigator) {
41 | owners.remove(navigator.key)?.forEach { it.value.onDispose(navigator) }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration/HiltDetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.hiltIntegration
2 |
3 | import androidx.compose.runtime.Composable
4 | import cafe.adriel.voyager.core.screen.Screen
5 | import cafe.adriel.voyager.core.screen.ScreenKey
6 | import cafe.adriel.voyager.core.screen.uniqueScreenKey
7 | import cafe.adriel.voyager.hilt.getScreenModel
8 | import cafe.adriel.voyager.navigator.LocalNavigator
9 | import cafe.adriel.voyager.navigator.currentOrThrow
10 | import cafe.adriel.voyager.sample.DetailsContent
11 |
12 | data class HiltDetailsScreen(
13 | val index: Int
14 | ) : Screen {
15 | override val key: ScreenKey = uniqueScreenKey
16 |
17 | @Composable
18 | override fun Content() {
19 | val navigator = LocalNavigator.currentOrThrow
20 |
21 | // Uncomment version below if you want keep using ViewModel instead of to convert it to ScreenModel
22 | // ViewModelProvider.Factory is not required. Until now Hilt has no support to Assisted Injection by default
23 | // val viewModel: HiltDetailsViewModel = getViewModel { factory -> factory.create(index) }
24 |
25 | // This version include more boilerplate because we are simulating support
26 | // to Assisted Injection using ScreenModel. See [HiltListScreen] for a simple version
27 | val viewModel = getScreenModel { factory ->
28 | factory.create(index)
29 | }
30 |
31 | DetailsContent(viewModel, "Item #${viewModel.index}", navigator::pop)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/voyager-navigator/src/androidMain/kotlin/cafe/adriel/voyager/navigator/NavigatorSaver.android.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.navigator
2 |
3 | import android.os.Parcelable
4 | import androidx.compose.runtime.saveable.listSaver
5 | import cafe.adriel.voyager.core.screen.Screen
6 |
7 | /**
8 | * Navigator Saver that forces all Screens be [Parcelable], if not, it will throw a exception while trying to save
9 | * the navigator state.
10 | */
11 | public fun parcelableNavigatorSaver(): NavigatorSaver =
12 | NavigatorSaver { _, key, stateHolder, disposeBehavior, parent ->
13 | listSaver(
14 | save = { navigator ->
15 | val screenAsParcelables = navigator.items.filterIsInstance()
16 |
17 | if (navigator.items.size > screenAsParcelables.size) {
18 | val screensNotParcelable =
19 | navigator.items.filterNot { screen -> screenAsParcelables.any { screen == it } }
20 | .map { it::class.simpleName }
21 | .joinToString()
22 |
23 | throw VoyagerNavigatorSaverException(
24 | "Unable to save instance state for Screens: $screensNotParcelable. " +
25 | "Implement android.os.Parcelable on your Screen."
26 | )
27 | }
28 |
29 | screenAsParcelables
30 | },
31 | restore = { items -> Navigator(items as List, key, stateHolder, disposeBehavior, parent) }
32 | )
33 | }
34 |
35 | public class VoyagerNavigatorSaverException(message: String) : RuntimeException(message)
36 |
--------------------------------------------------------------------------------
/docs/screenmodel/livedata-integration.md:
--------------------------------------------------------------------------------
1 | # LiveData integration
2 |
3 | !!! success
4 | To use the `LiveScreenModel` you should first import `cafe.adriel.voyager:voyager-livedata` (see [Setup](../setup.md)).
5 |
6 | ### State-aware ScreenModel
7 |
8 | If your `ScreenModel` needs to provide a state, use the `LiveScreenModel`. Set the initial state on the constructor and use `mutableState` to change the current state.
9 |
10 | ```kotlin
11 | class PostDetailsScreenModel(
12 | private val repository: PostRepository
13 | ) : LiveScreenModel(State.Init) {
14 |
15 | sealed class State {
16 | object Init : State()
17 | object Loading : State()
18 | data class Result(val post: Post) : State()
19 | }
20 |
21 | fun getPost(id: String) {
22 | coroutineScope.launch {
23 | val result = State.Result(post = repository.getPost(id))
24 | mutableState.postValue(result)
25 | }
26 | }
27 | }
28 | ```
29 |
30 | In your screen use `state.observeAsState()` and handle the current state.
31 |
32 | ```kotlin
33 | class PostDetailsScreen : Screen {
34 |
35 | @Composable
36 | override fun Content() {
37 | val screenModel = rememberScreenModel()
38 | val state by screenModel.state.observeAsState()
39 |
40 | when (state) {
41 | is State.Loading -> LoadingContent()
42 | is State.Result -> PostContent(state.post)
43 | }
44 | }
45 | }
46 | ```
47 |
48 | ### Sample
49 |
50 | !!! info
51 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/liveDataIntegration).
52 |
--------------------------------------------------------------------------------
/samples/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | alias(libs.plugins.kotlin.kapt)
6 | alias(libs.plugins.kotlin.parcelize)
7 | alias(libs.plugins.hilt)
8 | id("samples-module")
9 | }
10 |
11 | android {
12 | namespace = "cafe.adriel.voyager.sample"
13 | defaultConfig {
14 | applicationId = "cafe.adriel.voyager.sample"
15 | }
16 | }
17 |
18 | kapt {
19 | correctErrorTypes = true
20 | }
21 |
22 | dependencies {
23 | implementation(projects.voyagerScreenmodel)
24 | implementation(projects.voyagerNavigator)
25 | implementation(projects.voyagerTabNavigator)
26 | implementation(projects.voyagerBottomSheetNavigator)
27 | implementation(projects.voyagerTransitions)
28 | implementation(projects.voyagerHilt)
29 | implementation(projects.voyagerKodein)
30 | implementation(projects.voyagerKoin)
31 | implementation(projects.voyagerRxjava)
32 | implementation(projects.voyagerLivedata)
33 |
34 | kapt(libs.hilt.compiler)
35 | implementation(libs.hilt.android)
36 | implementation(libs.kodein)
37 | implementation(libs.androidx.lifecycle.viewModelKtx)
38 | implementation(libs.androidx.lifecycle.viewModelCompose)
39 | implementation(libs.androidx.activity.compose)
40 | implementation(samplesCatalog.koin)
41 | implementation(samplesCatalog.compose.rxjava)
42 | implementation(samplesCatalog.compose.livedata)
43 | implementation(samplesCatalog.compose.material)
44 | implementation(samplesCatalog.compose.materialIcons)
45 |
46 | debugImplementation(samplesCatalog.leakCanary)
47 | }
48 |
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | # Faq
2 |
3 | [iOS Swipe Back support](#ios-swipeback) { #ios-swipeback }
4 |
5 | Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all
6 | solutions that have out there and we think we will better of using a community made solution by copying
7 | to your code base and be able to change as you want your app to behave.
8 |
9 | See this [issue](https://github.com/adrielcafe/voyager/issues/144) each for community build solutions.
10 |
11 | Alternatively, we can also discuss in the future a community build solution using `NavigationController`
12 | under the hood like [`compose-cupertino`](https://github.com/alexzhirkevich/compose-cupertino/blob/master/cupertino-decompose/src/iosMain/kotlin/io/github/alexzhirkevich/cupertino/decompose/UIKitChildren.kt#L192) have implemented for Decompose.
13 |
14 | [Support for predictive back animations](#predictive-back) { #predictive-back }
15 |
16 | Voyager does not have a built in support for predictive back yet, but as well as iOS Swipe Back, the
17 | community have build extensions, and snippets with support, see
18 | [#223](https://github.com/adrielcafe/voyager/issues/223) and [144](https://github.com/adrielcafe/voyager/issues/144).
19 |
20 | [Support for result passing between screens](#result-passing) { #result-passing }
21 |
22 | Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all
23 | solutions that have out there, we encourage to use community made solutions by copying to your
24 | code base and being free to extend as your code base needs.
25 |
26 | See [#128](https://github.com/adrielcafe/voyager/pull/128) comments for community solutions.
27 |
28 | [Deeplink support](./deep-links.md)
29 |
--------------------------------------------------------------------------------
/samples/android/src/main/res/layout/activity_legacy.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
24 |
25 |
31 |
32 |
33 |
34 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/voyager-hilt/src/main/java/cafe/adriel/voyager/hilt/VoyagerHiltViewModelFactories.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.hilt
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.lifecycle.ViewModelProvider
5 | import dagger.hilt.EntryPoint
6 | import dagger.hilt.EntryPoints
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.android.components.ActivityComponent
9 | import dagger.hilt.android.internal.builders.ViewModelComponentBuilder
10 | import dagger.hilt.android.internal.lifecycle.HiltViewModelFactory
11 | import dagger.hilt.android.internal.lifecycle.HiltViewModelMap
12 | import javax.inject.Inject
13 |
14 | public object VoyagerHiltViewModelFactories {
15 |
16 | public fun getVoyagerFactory(
17 | activity: ComponentActivity,
18 | delegateFactory: ViewModelProvider.Factory
19 | ): ViewModelProvider.Factory {
20 | return EntryPoints.get(activity, ViewModelFactoryEntryPoint::class.java)
21 | .internalViewModelFactory()
22 | .fromActivity(delegateFactory)
23 | }
24 |
25 | internal class InternalViewModelFactory @Inject internal constructor(
26 | @HiltViewModelMap.KeySet private val keySet: Map, Boolean>,
27 | private val viewModelComponentBuilder: ViewModelComponentBuilder
28 | ) {
29 | fun fromActivity(
30 | delegateFactory: ViewModelProvider.Factory
31 | ): ViewModelProvider.Factory {
32 | return HiltViewModelFactory(keySet, delegateFactory, viewModelComponentBuilder)
33 | }
34 | }
35 |
36 | @EntryPoint
37 | @InstallIn(ActivityComponent::class)
38 | internal interface ViewModelFactoryEntryPoint {
39 | fun internalViewModelFactory(): InternalViewModelFactory
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule.posts
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.material.Button
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import cafe.adriel.voyager.core.screen.Screen
16 | import cafe.adriel.voyager.navigator.LocalNavigator
17 | import cafe.adriel.voyager.navigator.currentOrThrow
18 |
19 | class ListScreen : Screen {
20 |
21 | @Composable
22 | override fun Content() {
23 | val navigator = LocalNavigator.currentOrThrow
24 |
25 | Column(
26 | verticalArrangement = Arrangement.Center,
27 | horizontalAlignment = Alignment.CenterHorizontally,
28 | modifier = Modifier.fillMaxSize()
29 | ) {
30 | Text(
31 | text = "Post List",
32 | style = MaterialTheme.typography.h5
33 | )
34 |
35 | Spacer(modifier = Modifier.height(16.dp))
36 |
37 | Button(
38 | onClick = { navigator.pop() }
39 | ) {
40 | Text(
41 | text = "Return",
42 | style = MaterialTheme.typography.button
43 | )
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/samples/multiplatform-iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/samples/android/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/samples/multi-module/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/samples/multiplatform/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/voyager-screenmodel/src/commonMain/kotlin/cafe/adriel/voyager/core/model/ScreenModel.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.model
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisallowComposableCalls
5 | import androidx.compose.runtime.remember
6 | import cafe.adriel.voyager.core.concurrent.PlatformMainDispatcher
7 | import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore
8 | import cafe.adriel.voyager.core.screen.Screen
9 | import kotlinx.coroutines.CoroutineName
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.SupervisorJob
12 | import kotlinx.coroutines.cancel
13 | import kotlinx.coroutines.flow.MutableStateFlow
14 | import kotlinx.coroutines.flow.StateFlow
15 | import kotlinx.coroutines.flow.asStateFlow
16 |
17 | public val ScreenModel.screenModelScope: CoroutineScope
18 | get() = ScreenModelStore.getOrPutDependency(
19 | screenModel = this,
20 | name = "ScreenModelCoroutineScope",
21 | factory = { key -> CoroutineScope(SupervisorJob() + PlatformMainDispatcher + CoroutineName(key)) },
22 | onDispose = { scope -> scope.cancel() }
23 | )
24 |
25 | @Composable
26 | public inline fun Screen.rememberScreenModel(
27 | tag: String? = null,
28 | crossinline factory: @DisallowComposableCalls () -> T
29 | ): T {
30 | val screenModelStore = remember(this) {
31 | ScreenLifecycleStore.get(this) { ScreenModelStore }
32 | }
33 | return remember(screenModelStore.getKey(this, tag)) {
34 | screenModelStore.getOrPut(this, tag, factory)
35 | }
36 | }
37 |
38 | public interface ScreenModel {
39 |
40 | public fun onDispose() {}
41 | }
42 |
43 | public abstract class StateScreenModel(initialState: S) : ScreenModel {
44 |
45 | protected val mutableState: MutableStateFlow = MutableStateFlow(initialState)
46 | public val state: StateFlow = mutableState.asStateFlow()
47 | }
48 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/androidMain/kotlin/cafe/adriel/voyager/jetpack/AndroidScreenLifecycleOwner.android.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.ProvidedValue
7 | import androidx.compose.ui.platform.LocalContext
8 | import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
9 | import androidx.lifecycle.SavedStateViewModelFactory
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.lifecycle.viewmodel.MutableCreationExtras
12 | import androidx.savedstate.SavedStateRegistryOwner
13 | import java.util.concurrent.atomic.AtomicReference
14 |
15 | internal actual class SavedStateViewModelPlatform actual constructor(val owner: SavedStateRegistryOwner) {
16 | private val atomicAppContext = AtomicReference()
17 |
18 | actual fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory =
19 | SavedStateViewModelFactory(
20 | application = atomicAppContext.get()?.getApplication(),
21 | owner = owner
22 | )
23 |
24 | actual fun providePlatform(extras: MutableCreationExtras) {
25 | val application = atomicAppContext.get()?.getApplication()
26 | if (application != null) {
27 | extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, application)
28 | }
29 | }
30 |
31 | @Composable
32 | actual fun initHooks() {
33 | atomicAppContext.compareAndSet(null, LocalContext.current.applicationContext)
34 | }
35 |
36 | actual fun provideHooks(): List> = listOf(
37 | LocalSavedStateRegistryOwner provides owner,
38 | androidx.lifecycle.compose.LocalLifecycleOwner provides owner
39 | )
40 |
41 | private fun Context.getApplication(): Application? = when (this) {
42 | is Application -> this
43 | else -> null
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMap.js.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.locks.SynchronizedObject
4 | import kotlinx.atomicfu.locks.synchronized
5 |
6 | public actual class ThreadSafeMap(
7 | private val delegate: MutableMap
8 | ) : MutableMap {
9 | public actual constructor() : this(delegate = mutableMapOf())
10 | private val syncObject = SynchronizedObject()
11 |
12 | actual override val size: Int
13 | get() = synchronized(syncObject) { delegate.size }
14 |
15 | actual override fun containsKey(key: K): Boolean {
16 | return synchronized(syncObject) { delegate.containsKey(key) }
17 | }
18 |
19 | actual override fun containsValue(value: V): Boolean {
20 | return synchronized(syncObject) { delegate.containsValue(value) }
21 | }
22 |
23 | actual override fun get(key: K): V? {
24 | return synchronized(syncObject) { delegate[key] }
25 | }
26 |
27 | actual override fun isEmpty(): Boolean {
28 | return synchronized(syncObject) { delegate.isEmpty() }
29 | }
30 |
31 | actual override val entries: MutableSet>
32 | get() = delegate.entries
33 | actual override val keys: MutableSet
34 | get() = delegate.keys
35 | actual override val values: MutableCollection
36 | get() = delegate.values
37 |
38 | actual override fun clear() {
39 | synchronized(syncObject) { delegate.clear() }
40 | }
41 |
42 | actual override fun put(key: K, value: V): V? {
43 | return synchronized(syncObject) { delegate.put(key, value) }
44 | }
45 |
46 | actual override fun putAll(from: Map) {
47 | synchronized(syncObject) { delegate.putAll(from) }
48 | }
49 |
50 | actual override fun remove(key: K): V? {
51 | return synchronized(syncObject) { delegate.remove(key) }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample.multimodule.posts
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.material.Button
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 | import cafe.adriel.voyager.core.screen.Screen
16 | import cafe.adriel.voyager.navigator.LocalNavigator
17 | import cafe.adriel.voyager.navigator.currentOrThrow
18 |
19 | data class DetailsScreen(
20 | val id: String
21 | ) : Screen {
22 |
23 | @Composable
24 | override fun Content() {
25 | val navigator = LocalNavigator.currentOrThrow
26 |
27 | Column(
28 | verticalArrangement = Arrangement.Center,
29 | horizontalAlignment = Alignment.CenterHorizontally,
30 | modifier = Modifier.fillMaxSize()
31 | ) {
32 | Text(
33 | text = "Post Details",
34 | style = MaterialTheme.typography.h5
35 | )
36 |
37 | Spacer(modifier = Modifier.height(16.dp))
38 |
39 | Text(
40 | text = "ID: $id",
41 | style = MaterialTheme.typography.body1
42 | )
43 |
44 | Spacer(modifier = Modifier.height(16.dp))
45 |
46 | Button(
47 | onClick = { navigator.pop() }
48 | ) {
49 | Text(
50 | text = "Return",
51 | style = MaterialTheme.typography.button
52 | )
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/voyager-core/src/nativeMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeMutableCollection.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.locks.SynchronizedObject
4 | import kotlinx.atomicfu.locks.synchronized
5 |
6 | public open class ThreadSafeMutableCollectioninternal constructor(
7 | private val syncObject: SynchronizedObject,
8 | private val delegate: MutableCollection
9 | ) : MutableCollection {
10 |
11 | override val size: Int
12 | get() = synchronized(syncObject) { delegate.size }
13 |
14 | override fun contains(element: T): Boolean {
15 | return synchronized(syncObject) { delegate.contains(element) }
16 | }
17 |
18 | override fun containsAll(elements: Collection): Boolean {
19 | return synchronized(syncObject) { delegate.containsAll(elements) }
20 | }
21 |
22 | override fun isEmpty(): Boolean {
23 | return synchronized(syncObject) { delegate.isEmpty() }
24 | }
25 |
26 | override fun iterator(): MutableIterator {
27 | return synchronized(syncObject) { ThreadSafeMutableIterator(syncObject, delegate.iterator()) }
28 | }
29 |
30 | override fun add(element: T): Boolean {
31 | return synchronized(syncObject) { delegate.add(element) }
32 | }
33 |
34 | override fun addAll(elements: Collection): Boolean {
35 | return synchronized(syncObject) { delegate.addAll(elements) }
36 | }
37 |
38 | override fun clear() {
39 | return synchronized(syncObject) { delegate.clear() }
40 | }
41 |
42 | override fun remove(element: T): Boolean {
43 | return synchronized(syncObject) { delegate.remove(element) }
44 | }
45 |
46 | override fun removeAll(elements: Collection): Boolean {
47 | return synchronized(syncObject) { delegate.removeAll(elements) }
48 | }
49 |
50 | override fun retainAll(elements: Collection): Boolean {
51 | return synchronized(syncObject) { delegate.retainAll(elements) }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/voyager-core/src/commonWebMain/kotlin/cafe.adriel.voyager.core/concurrent/ThreadSafeSet.js.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.core.concurrent
2 |
3 | import kotlinx.atomicfu.locks.SynchronizedObject
4 | import kotlinx.atomicfu.locks.synchronized
5 |
6 | public actual class ThreadSafeSet(
7 | private val delegate: MutableSet
8 | ) : MutableSet {
9 | public actual constructor() : this(delegate = mutableSetOf())
10 | private val syncObject = SynchronizedObject()
11 |
12 | actual override val size: Int
13 | get() = delegate.size
14 |
15 | actual override fun contains(element: T): Boolean {
16 | return synchronized(syncObject) { delegate.contains(element) }
17 | }
18 |
19 | actual override fun containsAll(elements: Collection): Boolean {
20 | return synchronized(syncObject) { delegate.containsAll(elements) }
21 | }
22 |
23 | actual override fun isEmpty(): Boolean {
24 | return synchronized(syncObject) { delegate.isEmpty() }
25 | }
26 |
27 | actual override fun iterator(): MutableIterator {
28 | return synchronized(syncObject) { delegate.iterator() }
29 | }
30 |
31 | actual override fun add(element: T): Boolean {
32 | return synchronized(syncObject) { delegate.add(element) }
33 | }
34 |
35 | actual override fun addAll(elements: Collection): Boolean {
36 | return synchronized(syncObject) { delegate.addAll(elements) }
37 | }
38 |
39 | actual override fun clear() {
40 | return synchronized(syncObject) { delegate.clear() }
41 | }
42 |
43 | actual override fun remove(element: T): Boolean {
44 | return synchronized(syncObject) { delegate.remove(element) }
45 | }
46 |
47 | actual override fun removeAll(elements: Collection): Boolean {
48 | return synchronized(syncObject) { delegate.removeAll(elements) }
49 | }
50 |
51 | actual override fun retainAll(elements: Collection): Boolean {
52 | return synchronized(syncObject) { delegate.retainAll(elements) }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/samples/android/src/main/java/cafe/adriel/voyager/sample/App.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.sample
2 |
3 | import android.app.Application
4 | import cafe.adriel.voyager.kodein.ScreenLifecycleScope
5 | import cafe.adriel.voyager.sample.androidViewModel.AndroidDetailsViewModel
6 | import cafe.adriel.voyager.sample.androidViewModel.AndroidListViewModel
7 | import cafe.adriel.voyager.sample.kodeinIntegration.KodeinScopedDependencySample
8 | import cafe.adriel.voyager.sample.kodeinIntegration.KodeinScreenModel
9 | import cafe.adriel.voyager.sample.koinIntegration.KoinScreenModel
10 | import dagger.hilt.android.HiltAndroidApp
11 | import org.kodein.di.DI
12 | import org.kodein.di.DIAware
13 | import org.kodein.di.android.x.androidXModule
14 | import org.kodein.di.bind
15 | import org.kodein.di.bindProvider
16 | import org.kodein.di.scoped
17 | import org.kodein.di.singleton
18 | import org.koin.android.ext.koin.androidContext
19 | import org.koin.core.context.startKoin
20 | import org.koin.core.module.dsl.factoryOf
21 | import org.koin.core.module.dsl.viewModel
22 | import org.koin.dsl.module
23 |
24 | @HiltAndroidApp
25 | class App : Application(), DIAware {
26 |
27 | override val di by DI.lazy {
28 | androidXModule(this@App)
29 | bindProvider { KodeinScreenModel() }
30 | bind() with scoped(ScreenLifecycleScope).singleton {
31 | KodeinScopedDependencySample(context.screen.key)
32 | }
33 | }
34 |
35 | override fun onCreate() {
36 | super.onCreate()
37 | startKoin {
38 | androidContext(this@App)
39 | modules(
40 | module {
41 | factoryOf(::KoinScreenModel)
42 |
43 | viewModel {
44 | AndroidListViewModel(handle = get())
45 | }
46 | viewModel { parameters ->
47 | AndroidDetailsViewModel(index = parameters.get())
48 | }
49 | }
50 | )
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/samples/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/docs/screenmodel/hilt-integration.md:
--------------------------------------------------------------------------------
1 | # Hilt integration
2 |
3 | !!! success
4 | To use the `getScreenModel` you should first import `cafe.adriel.voyager:voyager-hilt` (see [Setup](../setup.md)).
5 |
6 | ### @Inject
7 |
8 | Add `@Inject` annotation to your `ScreenModel`.
9 |
10 | ```kotlin
11 | class HomeScreenModel @Inject constructor() : ScreenModel {
12 | // ...
13 | }
14 | ```
15 |
16 | Call `getScreenModel()` to get a new instance.
17 |
18 | ```kotlin
19 | class HomeScreen : AndroidScreen() {
20 |
21 | @Composable
22 | override fun Content() {
23 | val screenModel = getScreenModel()
24 | // ...
25 | }
26 | }
27 | ```
28 |
29 | ### @AssistedInject
30 |
31 | Add `@AssistedInject` annotation to your `ScreenModel` and provide a `ScreenModelFactory` annotated with `@AssistedFactory`.
32 |
33 | ```kotlin
34 | class PostDetailsScreenModel @AssistedInject constructor(
35 | @Assisted val postId: Long
36 | ) : ScreenModel {
37 |
38 | @AssistedFactory
39 | interface Factory : ScreenModelFactory {
40 | fun create(postId: Long): PostDetailsScreenModel
41 | }
42 | }
43 | ```
44 |
45 | Call `getScreenModel()` and use your factory to create a new instance.
46 |
47 | ```kotlin
48 | data class PostDetailsScreen(val postId: Long): AndroidScreen() {
49 |
50 | @Composable
51 | override fun Content() {
52 | val screenModel = getScreenModel { factory ->
53 | factory.create(postId)
54 | }
55 | // ...
56 | }
57 | }
58 | ```
59 |
60 | ### Sample
61 |
62 | !!! info
63 | Sample code [here](https://github.com/adrielcafe/voyager/tree/main/samples/android/src/main/java/cafe/adriel/voyager/sample/hiltIntegration).
64 |
65 | ### Navigator scoped ScreenModel
66 |
67 | ```kotlin
68 | class HomeScreen : Screen {
69 |
70 | @Composable
71 | override fun Content() {
72 | val navigator = LocalNavigator.currentOrThrow
73 | val screenModel = navigator.getNavigatorScreenModel()
74 | // ...
75 | }
76 | }
77 | ```
78 |
--------------------------------------------------------------------------------
/voyager-lifecycle-kmp/src/commonMain/kotlin/cafe/adriel/voyager/jetpack/navigator.kt:
--------------------------------------------------------------------------------
1 | package cafe.adriel.voyager.jetpack
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.lifecycle.HasDefaultViewModelProviderFactory
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.ViewModelProvider
7 | import androidx.lifecycle.ViewModelStoreOwner
8 | import androidx.lifecycle.viewmodel.CreationExtras
9 | import androidx.lifecycle.viewmodel.compose.viewModel
10 | import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
11 | import cafe.adriel.voyager.navigator.LocalNavigator
12 | import cafe.adriel.voyager.navigator.Navigator
13 | import cafe.adriel.voyager.navigator.currentOrThrow
14 |
15 | @ExperimentalVoyagerApi
16 | @Composable
17 | public fun Navigator.rememberNavigatorViewModelStoreOwner(): ViewModelStoreOwner {
18 | return rememberNavigatorLifecycleOwner()
19 | }
20 |
21 | @ExperimentalVoyagerApi
22 | @Composable
23 | public inline fun navigatorViewModel(
24 | viewModelStoreOwner: ViewModelStoreOwner = LocalNavigator.currentOrThrow
25 | .rememberNavigatorViewModelStoreOwner(),
26 | key: String? = null,
27 | noinline initializer: CreationExtras.() -> VM
28 | ): VM {
29 | return viewModel(
30 | viewModelStoreOwner = viewModelStoreOwner,
31 | key = key,
32 | initializer = initializer
33 | )
34 | }
35 |
36 | @ExperimentalVoyagerApi
37 | @Composable
38 | public inline fun navigatorViewModel(
39 | viewModelStoreOwner: ViewModelStoreOwner = LocalNavigator.currentOrThrow
40 | .rememberNavigatorViewModelStoreOwner(),
41 | key: String? = null,
42 | factory: ViewModelProvider.Factory? = null,
43 | extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) {
44 | viewModelStoreOwner.defaultViewModelCreationExtras
45 | } else {
46 | CreationExtras.Empty
47 | }
48 | ): VM = viewModel(
49 | viewModelStoreOwner = viewModelStoreOwner,
50 | key = key,
51 | factory = factory,
52 | extras = extras
53 | )
54 |
--------------------------------------------------------------------------------