From d146e0128a1768787036647c0d8edb17f0c06bea Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Mon, 15 Apr 2024 17:03:04 -0700 Subject: [PATCH 1/3] Recreate nested nav to work with AnimatedPane Change-Id: I6b526331b7fc62b968ac39e91753a8a1e5343023 --- .../InterestsListDetailScreen.kt | 82 +++++++++++++------ .../topic/navigation/TopicNavigation.kt | 9 +- gradle/libs.versions.toml | 2 +- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 4cc4345ef..87a6f48fa 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -18,14 +18,19 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane import androidx.activity.compose.BackHandler import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder @@ -39,8 +44,10 @@ import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERES import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE +import com.google.samples.apps.nowinandroid.feature.topic.navigation.createTopicRoute import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen +import java.util.UUID private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route" @@ -76,17 +83,38 @@ internal fun InterestsListDetailScreen( selectedTopicId: String?, onTopicClick: (String) -> Unit, ) { - val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() + val listDetailNavigator = rememberListDetailPaneScaffoldNavigator( + initialDestinationHistory = listOfNotNull( + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List), + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail).takeIf { + selectedTopicId != null + }, + ), + ) BackHandler(listDetailNavigator.canNavigateBack()) { listDetailNavigator.navigateBack() } - val nestedNavController = rememberNavController() + var nestedNavHostStartDestination by remember { + mutableStateOf(selectedTopicId?.let(::createTopicRoute) ?: TOPIC_ROUTE) + } + var nestedNavKey by remember { mutableStateOf(UUID.randomUUID()) } + val nestedNavController = key(nestedNavKey) { + rememberNavController() + } fun onTopicClickShowDetailPane(topicId: String) { onTopicClick(topicId) - nestedNavController.navigateToTopic(topicId) { - popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + if (listDetailNavigator.isDetailPaneVisible()) { + // If the detail pane was visible, then use the nestedNavController navigate call + // directly + nestedNavController.navigateToTopic(topicId) { + popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + } + } else { + // Otherwise, recreate the NavHost entirely, and start at the new destination + nestedNavHostStartDestination = createTopicRoute(topicId) + nestedNavKey = UUID.randomUUID() } listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) } @@ -95,34 +123,34 @@ internal fun InterestsListDetailScreen( value = listDetailNavigator.scaffoldValue, directive = listDetailNavigator.scaffoldDirective, listPane = { - InterestsRoute( - onTopicClick = ::onTopicClickShowDetailPane, - highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), - ) - }, - detailPane = { - NavHost( - navController = nestedNavController, - startDestination = TOPIC_ROUTE, - route = DETAIL_PANE_NAVHOST_ROUTE, - ) { - topicScreen( - showBackButton = !listDetailNavigator.isListPaneVisible(), - onBackClick = listDetailNavigator::navigateBack, + AnimatedPane { + InterestsRoute( onTopicClick = ::onTopicClickShowDetailPane, + highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), ) - composable(route = TOPIC_ROUTE) { - TopicDetailPlaceholder() + } + }, + detailPane = { + AnimatedPane { + key(nestedNavKey) { + NavHost( + navController = nestedNavController, + startDestination = nestedNavHostStartDestination, + route = DETAIL_PANE_NAVHOST_ROUTE, + ) { + topicScreen( + showBackButton = !listDetailNavigator.isListPaneVisible(), + onBackClick = listDetailNavigator::navigateBack, + onTopicClick = ::onTopicClickShowDetailPane, + ) + composable(route = TOPIC_ROUTE) { + TopicDetailPlaceholder() + } + } } } }, ) - LaunchedEffect(Unit) { - if (selectedTopicId != null) { - // Initial topic ID was provided when navigating to Interests, so show its details. - onTopicClickShowDetailPane(selectedTopicId) - } - } } @OptIn(ExperimentalMaterial3AdaptiveApi::class) diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt index 41804b634..394c53303 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt @@ -41,13 +41,16 @@ internal class TopicArgs(val topicId: String) { } fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) { - val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) - val newRoute = "$TOPIC_ROUTE/$encodedId" - navigate(newRoute) { + navigate(createTopicRoute(topicId)) { navOptions() } } +fun createTopicRoute(topicId: String): String { + val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) + return "$TOPIC_ROUTE/$encodedId" +} + fun NavGraphBuilder.topicScreen( showBackButton: Boolean, onBackClick: () -> Unit, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ef84555fa..6bc85e10e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.7.0" androidxMacroBenchmark = "1.2.2" androidxMetrics = "1.0.0-alpha04" -androidxNavigation = "2.7.7" +androidxNavigation = "2.8.0-alpha06" androidxProfileinstaller = "1.3.1" androidxTestCore = "1.5.0" androidxTestExt = "1.1.5" From 60d42b26466d4396dbf9c1760db4ed62c8c60c26 Mon Sep 17 00:00:00 2001 From: alexvanyo Date: Tue, 16 Apr 2024 00:34:30 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A4=96=20Updates=20baselines=20for=20?= =?UTF-8?q?Dependency=20Guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prodReleaseRuntimeClasspath.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 71e0ab289..1703b0c36 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -17,10 +17,10 @@ androidx.compose.animation:animation-android:1.7.0-alpha06 androidx.compose.animation:animation-core-android:1.7.0-alpha06 androidx.compose.animation:animation-core:1.7.0-alpha06 androidx.compose.animation:animation:1.7.0-alpha06 -androidx.compose.foundation:foundation-android:1.6.3 -androidx.compose.foundation:foundation-layout-android:1.6.3 -androidx.compose.foundation:foundation-layout:1.6.3 -androidx.compose.foundation:foundation:1.6.3 +androidx.compose.foundation:foundation-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout:1.7.0-alpha06 +androidx.compose.foundation:foundation:1.7.0-alpha06 androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha10 androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha10 androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha10 @@ -103,11 +103,11 @@ androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha04 androidx.loader:loader:1.0.0 androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 androidx.metrics:metrics-performance:1.0.0-alpha04 -androidx.navigation:navigation-common-ktx:2.7.7 -androidx.navigation:navigation-common:2.7.7 -androidx.navigation:navigation-compose:2.7.7 -androidx.navigation:navigation-runtime-ktx:2.7.7 -androidx.navigation:navigation-runtime:2.7.7 +androidx.navigation:navigation-common-ktx:2.8.0-alpha06 +androidx.navigation:navigation-common:2.8.0-alpha06 +androidx.navigation:navigation-compose:2.8.0-alpha06 +androidx.navigation:navigation-runtime-ktx:2.8.0-alpha06 +androidx.navigation:navigation-runtime:2.8.0-alpha06 androidx.print:print:1.0.0 androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 From 80676d55ffc04302f950b8d8141348d64bd8ea11 Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Tue, 16 Apr 2024 10:45:45 -0700 Subject: [PATCH 3/3] Save nested nav key in instance state Change-Id: If1155bfbe080eb4df3c59faaec0fb4cd4da3821d --- .../ui/interests2pane/Interests2PaneViewModel.kt | 3 ++- .../ui/interests2pane/InterestsListDetailScreen.kt | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt index d618c2d47..40ce9c116 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt @@ -27,7 +27,8 @@ import javax.inject.Inject class Interests2PaneViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { - val selectedTopicId: StateFlow = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null) + val selectedTopicId: StateFlow = + savedStateHandle.getStateFlow(TOPIC_ID_ARG, savedStateHandle[TOPIC_ID_ARG]) fun onTopicClick(topicId: String?) { savedStateHandle[TOPIC_ID_ARG] = topicId diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 87a6f48fa..ada4e49d1 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -30,6 +30,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -98,7 +100,11 @@ internal fun InterestsListDetailScreen( var nestedNavHostStartDestination by remember { mutableStateOf(selectedTopicId?.let(::createTopicRoute) ?: TOPIC_ROUTE) } - var nestedNavKey by remember { mutableStateOf(UUID.randomUUID()) } + var nestedNavKey by rememberSaveable( + stateSaver = Saver({ it.toString() }, UUID::fromString), + ) { + mutableStateOf(UUID.randomUUID()) + } val nestedNavController = key(nestedNavKey) { rememberNavController() }