diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d017f73ad..7f8faa9ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -103,6 +103,7 @@ wear = "1.4.0" wearComposeFoundation = "1.6.1" wearComposeMaterial = "1.6.1" wearComposeMaterial3 = "1.6.1" +wearComposeNavigation3 = "1.6.0" wearInput = "1.2.0" wearOngoing = "1.1.0" wearPhoneInteractions = "1.1.0" @@ -286,6 +287,7 @@ truth = { module = "com.google.truth:truth", version.ref = "truth" } validator-push = { module = "com.google.android.wearable.watchface.validator:validator-push", version.ref = "validatorPush" } wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" } wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" } +wear-compose-navigation3 = { module = "androidx.wear.compose:compose-navigation3", version.ref = "wearComposeNavigation3" } androidx-core-telecom = { group = "androidx.core", name = "core-telecom", version.ref = "androidx-core-telecom" } androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Session" } play-services-fitness = { group = "com.google.android.gms", name = "play-services-fitness", version.ref = "playServicesFitness" } diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index db52932b9..10635fb1f 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.compose.compiler) alias(libs.plugins.roborazzi) + alias(libs.plugins.kotlin.serialization) } kotlin { @@ -139,4 +140,10 @@ dependencies { debugImplementation(libs.androidx.compose.ui.tooling.preview) debugImplementation(libs.androidx.compose.ui.test.manifest) + // Navigation 3 + implementation(libs.wear.compose.navigation3) + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) + implementation(libs.androidx.lifecycle.viewmodel.navigation3) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/wear/src/main/java/com/example/wear/snippets/navigation3/Migration.kt b/wear/src/main/java/com/example/wear/snippets/navigation3/Migration.kt new file mode 100644 index 000000000..a48e25826 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/navigation3/Migration.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.wear.snippets.navigation3 + +import androidx.compose.runtime.Composable +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay +import androidx.wear.compose.material3.ScreenScaffold +import androidx.wear.compose.material3.Text +import androidx.wear.compose.navigation3.rememberSwipeDismissableSceneStrategy +import kotlinx.serialization.Serializable +import androidx.wear.compose.navigation.SwipeDismissableNavHost +import androidx.wear.compose.navigation.composable +import androidx.wear.compose.navigation.rememberSwipeDismissableNavController + +// [START android_wear_navigation3_migration_destinations_nav3] +@Serializable +sealed interface MigrationScreen : NavKey { + @Serializable + data object Landing : MigrationScreen + + @Serializable + data object List : MigrationScreen +} +// [END android_wear_navigation3_migration_destinations_nav3] + +@Composable +fun MigrationApp() { + val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey) + val strategy = rememberSwipeDismissableSceneStrategy() + + // [START android_wear_navigation3_migration_setup_nav3] + NavDisplay( + backStack = backStack, + sceneStrategies = listOf(strategy), + entryProvider = entryProvider { + entry { + GreetingScreen( + onShowList = { backStack.add(MigrationScreen.List) } + ) + } + entry { + ListScreen() + } + } + ) + // [END android_wear_navigation3_migration_setup_nav3] +} + +// [START_EXCLUDE] +@Composable +fun GreetingScreen(onShowList: () -> Unit) { + ScreenScaffold { + Text("Greeting Screen") + } +} + +@Composable +fun ListScreen() { + ScreenScaffold { + Text("List Screen") + } +} +// [END_EXCLUDE] + +// [START android_wear_navigation3_migration_destinations_nav2] +sealed class Nav2Screen { + data object Landing : Nav2Screen() + data object List : Nav2Screen() +} +// [END android_wear_navigation3_migration_destinations_nav2] + +@Composable +fun Nav2Init() { + // [START android_wear_navigation3_migration_init_nav2] + val navController = rememberSwipeDismissableNavController() + // [END android_wear_navigation3_migration_init_nav2] +} + +@Composable +fun Nav3Init() { + // [START android_wear_navigation3_migration_init_nav3] + val backStack = rememberNavBackStack(MigrationScreen.Landing as NavKey) + val strategy = rememberSwipeDismissableSceneStrategy() + // [END android_wear_navigation3_migration_init_nav3] +} + +@Composable +fun Nav2Setup() { + val navController = rememberSwipeDismissableNavController() + // [START android_wear_navigation3_migration_setup_nav2] + SwipeDismissableNavHost(navController = navController, startDestination = "menu") { + composable("menu") { + GreetingScreen( + onShowList = { navController.navigate("list") } + ) + } + composable("list") { + ListScreen() + } + } + // [END android_wear_navigation3_migration_setup_nav2] +} diff --git a/wear/src/main/java/com/example/wear/snippets/navigation3/Navigation.kt b/wear/src/main/java/com/example/wear/snippets/navigation3/Navigation.kt new file mode 100644 index 000000000..354b64760 --- /dev/null +++ b/wear/src/main/java/com/example/wear/snippets/navigation3/Navigation.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.wear.snippets.navigation3 + +import androidx.compose.runtime.Composable +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.ui.NavDisplay +import androidx.wear.compose.material3.ScreenScaffold +import androidx.wear.compose.material3.Text +import androidx.wear.compose.navigation3.rememberSwipeDismissableSceneStrategy +import kotlinx.serialization.Serializable +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator + +// [START android_wear_navigation3_destinations] +@Serializable +sealed interface Screen : NavKey { + @Serializable + data object Home : Screen + + @Serializable + data class Details(val itemId: String) : Screen +} +// [END android_wear_navigation3_destinations] + +@Composable +fun WearApp() { + // [START android_wear_navigation3_setup] + // 1. Create the persistent back stack starting at the Home screen + val backStack = rememberNavBackStack(Screen.Home) + + // 2. Initialize the Wear OS swipe-to-dismiss strategy + val strategy = rememberSwipeDismissableSceneStrategy() + + // 3. Render the NavDisplay + NavDisplay( + backStack = backStack, + sceneStrategies = listOf(strategy), + entryProvider = entryProvider { + // 4. Map keys to Composables + entry { + HomeScreen( + onNavigateToDetails = { id -> backStack.add(Screen.Details(id)) } + ) + } + entry { key -> + DetailsScreen( + itemId = key.itemId, + onBack = { backStack.removeAt(backStack.lastIndex) } + ) + } + } + ) + // [END android_wear_navigation3_setup] +} + +// [START_EXCLUDE] +@Composable +fun HomeScreen(onNavigateToDetails: (String) -> Unit) { + ScreenScaffold { + Text("Home Screen") + } +} + +@Composable +fun DetailsScreen(itemId: String, onBack: () -> Unit) { + ScreenScaffold { + Text("Details Screen for item $itemId") + } +} + +class HomeViewModel : androidx.lifecycle.ViewModel() +// [END_EXCLUDE] + +@Composable +fun WearAppWithViewModel() { + val backStack = rememberNavBackStack(Screen.Home) + val strategy = rememberSwipeDismissableSceneStrategy() + + // [START android_wear_navigation3_viewmodel] + NavDisplay( + backStack = backStack, + sceneStrategies = listOf(strategy), + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator() + ), + entryProvider = entryProvider { + entry { + // Any viewModel() requested here will be scoped to this NavEntry + val viewModel: HomeViewModel = viewModel() + // [START_EXCLUDE silent] + HomeScreen(onNavigateToDetails = {}) + // [END_EXCLUDE] + } + } + ) + // [END android_wear_navigation3_viewmodel] +}