├── .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 | ![](../media/assets/navigation-android.gif) 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 | ![](../media/assets/nested-nav.gif) 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 | --------------------------------------------------------------------------------