Skip to content

Commit 3dd4240

Browse files
authored
Merge pull request #1380 from android/av/nested-nav-with-animated-pane
Recreate nested nav to work with AnimatedPane
2 parents afad1b9 + c2fc34c commit 3dd4240

File tree

5 files changed

+79
-41
lines changed

5 files changed

+79
-41
lines changed

app/dependencies/prodReleaseRuntimeClasspath.txt

+9-9
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ androidx.compose.animation:animation-android:1.7.0-alpha06
1717
androidx.compose.animation:animation-core-android:1.7.0-alpha06
1818
androidx.compose.animation:animation-core:1.7.0-alpha06
1919
androidx.compose.animation:animation:1.7.0-alpha06
20-
androidx.compose.foundation:foundation-android:1.6.3
21-
androidx.compose.foundation:foundation-layout-android:1.6.3
22-
androidx.compose.foundation:foundation-layout:1.6.3
23-
androidx.compose.foundation:foundation:1.6.3
20+
androidx.compose.foundation:foundation-android:1.7.0-alpha06
21+
androidx.compose.foundation:foundation-layout-android:1.7.0-alpha06
22+
androidx.compose.foundation:foundation-layout:1.7.0-alpha06
23+
androidx.compose.foundation:foundation:1.7.0-alpha06
2424
androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha10
2525
androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha10
2626
androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha10
@@ -103,11 +103,11 @@ androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha04
103103
androidx.loader:loader:1.0.0
104104
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
105105
androidx.metrics:metrics-performance:1.0.0-alpha04
106-
androidx.navigation:navigation-common-ktx:2.7.7
107-
androidx.navigation:navigation-common:2.7.7
108-
androidx.navigation:navigation-compose:2.7.7
109-
androidx.navigation:navigation-runtime-ktx:2.7.7
110-
androidx.navigation:navigation-runtime:2.7.7
106+
androidx.navigation:navigation-common-ktx:2.8.0-alpha06
107+
androidx.navigation:navigation-common:2.8.0-alpha06
108+
androidx.navigation:navigation-compose:2.8.0-alpha06
109+
androidx.navigation:navigation-runtime-ktx:2.8.0-alpha06
110+
androidx.navigation:navigation-runtime:2.8.0-alpha06
111111
androidx.print:print:1.0.0
112112
androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
113113
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05

app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import javax.inject.Inject
2727
class Interests2PaneViewModel @Inject constructor(
2828
private val savedStateHandle: SavedStateHandle,
2929
) : ViewModel() {
30-
val selectedTopicId: StateFlow<String?> = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null)
30+
val selectedTopicId: StateFlow<String?> =
31+
savedStateHandle.getStateFlow(TOPIC_ID_ARG, savedStateHandle[TOPIC_ID_ARG])
3132

3233
fun onTopicClick(topicId: String?) {
3334
savedStateHandle[TOPIC_ID_ARG] = topicId

app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt

+61-27
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,21 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane
1818

1919
import androidx.activity.compose.BackHandler
2020
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
21+
import androidx.compose.material3.adaptive.layout.AnimatedPane
2122
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
2223
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
2324
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
25+
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
2426
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
2527
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
2628
import androidx.compose.runtime.Composable
27-
import androidx.compose.runtime.LaunchedEffect
2829
import androidx.compose.runtime.getValue
30+
import androidx.compose.runtime.key
31+
import androidx.compose.runtime.mutableStateOf
32+
import androidx.compose.runtime.remember
33+
import androidx.compose.runtime.saveable.Saver
34+
import androidx.compose.runtime.saveable.rememberSaveable
35+
import androidx.compose.runtime.setValue
2936
import androidx.hilt.navigation.compose.hiltViewModel
3037
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3138
import androidx.navigation.NavGraphBuilder
@@ -39,8 +46,10 @@ import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERES
3946
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG
4047
import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
4148
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE
49+
import com.google.samples.apps.nowinandroid.feature.topic.navigation.createTopicRoute
4250
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
4351
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
52+
import java.util.UUID
4453

4554
private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route"
4655

@@ -76,17 +85,42 @@ internal fun InterestsListDetailScreen(
7685
selectedTopicId: String?,
7786
onTopicClick: (String) -> Unit,
7887
) {
79-
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator()
88+
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
89+
initialDestinationHistory = listOfNotNull(
90+
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
91+
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {
92+
selectedTopicId != null
93+
},
94+
),
95+
)
8096
BackHandler(listDetailNavigator.canNavigateBack()) {
8197
listDetailNavigator.navigateBack()
8298
}
8399

84-
val nestedNavController = rememberNavController()
100+
var nestedNavHostStartDestination by remember {
101+
mutableStateOf(selectedTopicId?.let(::createTopicRoute) ?: TOPIC_ROUTE)
102+
}
103+
var nestedNavKey by rememberSaveable(
104+
stateSaver = Saver({ it.toString() }, UUID::fromString),
105+
) {
106+
mutableStateOf(UUID.randomUUID())
107+
}
108+
val nestedNavController = key(nestedNavKey) {
109+
rememberNavController()
110+
}
85111

86112
fun onTopicClickShowDetailPane(topicId: String) {
87113
onTopicClick(topicId)
88-
nestedNavController.navigateToTopic(topicId) {
89-
popUpTo(DETAIL_PANE_NAVHOST_ROUTE)
114+
if (listDetailNavigator.isDetailPaneVisible()) {
115+
// If the detail pane was visible, then use the nestedNavController navigate call
116+
// directly
117+
nestedNavController.navigateToTopic(topicId) {
118+
popUpTo(DETAIL_PANE_NAVHOST_ROUTE)
119+
}
120+
} else {
121+
// Otherwise, recreate the NavHost entirely, and start at the new destination
122+
nestedNavHostStartDestination = createTopicRoute(topicId)
123+
nestedNavKey = UUID.randomUUID()
90124
}
91125
listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
92126
}
@@ -95,34 +129,34 @@ internal fun InterestsListDetailScreen(
95129
value = listDetailNavigator.scaffoldValue,
96130
directive = listDetailNavigator.scaffoldDirective,
97131
listPane = {
98-
InterestsRoute(
99-
onTopicClick = ::onTopicClickShowDetailPane,
100-
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
101-
)
102-
},
103-
detailPane = {
104-
NavHost(
105-
navController = nestedNavController,
106-
startDestination = TOPIC_ROUTE,
107-
route = DETAIL_PANE_NAVHOST_ROUTE,
108-
) {
109-
topicScreen(
110-
showBackButton = !listDetailNavigator.isListPaneVisible(),
111-
onBackClick = listDetailNavigator::navigateBack,
132+
AnimatedPane {
133+
InterestsRoute(
112134
onTopicClick = ::onTopicClickShowDetailPane,
135+
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
113136
)
114-
composable(route = TOPIC_ROUTE) {
115-
TopicDetailPlaceholder()
137+
}
138+
},
139+
detailPane = {
140+
AnimatedPane {
141+
key(nestedNavKey) {
142+
NavHost(
143+
navController = nestedNavController,
144+
startDestination = nestedNavHostStartDestination,
145+
route = DETAIL_PANE_NAVHOST_ROUTE,
146+
) {
147+
topicScreen(
148+
showBackButton = !listDetailNavigator.isListPaneVisible(),
149+
onBackClick = listDetailNavigator::navigateBack,
150+
onTopicClick = ::onTopicClickShowDetailPane,
151+
)
152+
composable(route = TOPIC_ROUTE) {
153+
TopicDetailPlaceholder()
154+
}
155+
}
116156
}
117157
}
118158
},
119159
)
120-
LaunchedEffect(Unit) {
121-
if (selectedTopicId != null) {
122-
// Initial topic ID was provided when navigating to Interests, so show its details.
123-
onTopicClickShowDetailPane(selectedTopicId)
124-
}
125-
}
126160
}
127161

128162
@OptIn(ExperimentalMaterial3AdaptiveApi::class)

feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt

+6-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ internal class TopicArgs(val topicId: String) {
4141
}
4242

4343
fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) {
44-
val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING)
45-
val newRoute = "$TOPIC_ROUTE/$encodedId"
46-
navigate(newRoute) {
44+
navigate(createTopicRoute(topicId)) {
4745
navOptions()
4846
}
4947
}
5048

49+
fun createTopicRoute(topicId: String): String {
50+
val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING)
51+
return "$TOPIC_ROUTE/$encodedId"
52+
}
53+
5154
fun NavGraphBuilder.topicScreen(
5255
showBackButton: Boolean,
5356
onBackClick: () -> Unit,

gradle/libs.versions.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ androidxHiltNavigationCompose = "1.2.0"
2020
androidxLifecycle = "2.7.0"
2121
androidxMacroBenchmark = "1.2.2"
2222
androidxMetrics = "1.0.0-alpha04"
23-
androidxNavigation = "2.7.7"
23+
androidxNavigation = "2.8.0-alpha06"
2424
androidxProfileinstaller = "1.3.1"
2525
androidxTestCore = "1.5.0"
2626
androidxTestExt = "1.1.5"

0 commit comments

Comments
 (0)