diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f0dc999b2..22239cc8f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -108,6 +108,8 @@ dependencies {
androidTestImplementation(project(":core-datastore-test"))
androidTestImplementation(project(":core-data-test"))
androidTestImplementation(project(":core-network"))
+ androidTestImplementation(libs.androidx.navigation.testing)
+ debugImplementation(libs.androidx.compose.ui.testManifest)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
diff --git a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt
new file mode 100644
index 000000000..597c17bcf
--- /dev/null
+++ b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 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.google.samples.apps.nowinandroid.ui
+
+import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
+import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.ComposeNavigator
+import androidx.navigation.compose.composable
+import androidx.navigation.createGraph
+import androidx.navigation.testing.TestNavHostController
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Tests [NiaAppState].
+ *
+ * Note: This could become an unit test if Robolectric is added to the project and the Context
+ * is faked.
+ */
+@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
+class NiaAppStateTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private lateinit var state: NiaAppState
+
+ @Test
+ fun niaAppState_currentDestination() {
+ var currentDestination: String? = null
+
+ composeTestRule.setContent {
+ val navController = rememberTestNavController()
+ state = remember(navController) {
+ NiaAppState(
+ windowSizeClass = getCompactWindowClass(),
+ navController = navController
+ )
+ }
+
+ // Update currentDestination whenever it changes
+ currentDestination = state.currentDestination?.route
+
+ // Navigate to destination b once
+ LaunchedEffect(Unit) {
+ navController.setCurrentDestination("b")
+ }
+ }
+
+ assertEquals("b", currentDestination)
+ }
+
+ @Test
+ fun niaAppState_destinations() {
+ composeTestRule.setContent {
+ state = rememberNiaAppState(getCompactWindowClass())
+ }
+
+ assertEquals(2, state.topLevelDestinations.size)
+ assertTrue(state.topLevelDestinations[0].destination.contains("for_you"))
+ assertTrue(state.topLevelDestinations[1].destination.contains("interests"))
+ }
+
+ @Test
+ fun niaAppState_showBottomBar_compact() {
+ composeTestRule.setContent {
+ state = NiaAppState(
+ windowSizeClass = getCompactWindowClass(),
+ navController = NavHostController(LocalContext.current)
+ )
+ }
+
+ assertTrue(state.shouldShowBottomBar)
+ assertFalse(state.shouldShowNavRail)
+ }
+
+ @Test
+ fun niaAppState_showNavRail_medium() {
+ composeTestRule.setContent {
+ state = NiaAppState(
+ windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(800.dp, 800.dp)),
+ navController = NavHostController(LocalContext.current)
+ )
+ }
+
+ assertTrue(state.shouldShowNavRail)
+ assertFalse(state.shouldShowBottomBar)
+ }
+
+ @Test
+ fun niaAppState_showNavRail_large() {
+ composeTestRule.setContent {
+ state = NiaAppState(
+ windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(900.dp, 1200.dp)),
+ navController = NavHostController(LocalContext.current)
+ )
+ }
+
+ assertTrue(state.shouldShowNavRail)
+ assertFalse(state.shouldShowBottomBar)
+ }
+
+ private fun getCompactWindowClass() = WindowSizeClass.calculateFromSize(DpSize(500.dp, 300.dp))
+}
+
+@Composable
+private fun rememberTestNavController(): TestNavHostController {
+ val context = LocalContext.current
+ val navController = remember {
+ TestNavHostController(context).apply {
+ navigatorProvider.addNavigator(ComposeNavigator())
+ graph = createGraph(startDestination = "a") {
+ composable("a") { }
+ composable("b") { }
+ composable("c") { }
+ }
+ }
+ }
+ return navController
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6d452a83e..f21692f5f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
Unit,
+ onBackClick: () -> Unit,
windowSizeClass: WindowSizeClass,
modifier: Modifier = Modifier,
- navController: NavHostController = rememberNavController(),
startDestination: String = ForYouDestination.route
) {
NavHost(
@@ -53,11 +55,19 @@ fun NiaNavHost(
windowSizeClass = windowSizeClass
)
interestsGraph(
- navigateToTopic = { navController.navigate("${TopicDestination.route}/$it") },
- navigateToAuthor = { navController.navigate("${AuthorDestination.route}/$it") },
+ navigateToTopic = {
+ onNavigateToDestination(
+ TopicDestination, TopicDestination.createNavigationRoute(it)
+ )
+ },
+ navigateToAuthor = {
+ onNavigateToDestination(
+ AuthorDestination, AuthorDestination.createNavigationRoute(it)
+ )
+ },
nestedGraphs = {
- topicGraph(onBackClick = { navController.popBackStack() })
- authorGraph(onBackClick = { navController.popBackStack() })
+ topicGraph(onBackClick)
+ authorGraph(onBackClick)
}
)
}
diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/NiaTopLevelNavigation.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/NiaTopLevelNavigation.kt
deleted file mode 100644
index 54d7bd402..000000000
--- a/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/NiaTopLevelNavigation.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2022 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.google.samples.apps.nowinandroid.navigation
-
-import androidx.navigation.NavGraph.Companion.findStartDestination
-import androidx.navigation.NavHostController
-import androidx.tracing.trace
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.DrawableResourceIcon
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVectorIcon
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
-import com.google.samples.apps.nowinandroid.feature.foryou.R.string.for_you
-import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination
-import com.google.samples.apps.nowinandroid.feature.interests.R.string.interests
-import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination
-
-/**
- * Routes for the different top level destinations in the application. Each of these destinations
- * can contain one or more screens (based on the window size). Navigation from one screen to the
- * next within a single destination will be handled directly in composables.
- */
-
-/**
- * Models the navigation top level actions in the app.
- */
-class NiaTopLevelNavigation(private val navController: NavHostController) {
-
- fun navigateTo(destination: TopLevelDestination) {
- trace("Navigation: $destination") {
- navController.navigate(destination.route) {
- // Pop up to the start destination of the graph to
- // avoid building up a large stack of destinations
- // on the back stack as users select items
- popUpTo(navController.graph.findStartDestination().id) {
- saveState = true
- }
- // Avoid multiple copies of the same destination when
- // reselecting the same item
- launchSingleTop = true
- // Restore state when reselecting a previously selected item
- restoreState = true
- }
- }
- }
-}
-
-data class TopLevelDestination(
- val route: String,
- val selectedIcon: Icon,
- val unselectedIcon: Icon,
- val iconTextId: Int
-)
-
-val TOP_LEVEL_DESTINATIONS = listOf(
- TopLevelDestination(
- route = ForYouDestination.route,
- selectedIcon = DrawableResourceIcon(NiaIcons.Upcoming),
- unselectedIcon = DrawableResourceIcon(NiaIcons.UpcomingBorder),
- iconTextId = for_you
- ),
- TopLevelDestination(
- route = InterestsDestination.route,
- selectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
- unselectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
- iconTextId = interests
- )
-)
diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt
new file mode 100644
index 000000000..4a2523bb5
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 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.google.samples.apps.nowinandroid.navigation
+
+import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon
+import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigationDestination
+
+/**
+ * Type for the top level destinations in the application. Each of these destinations
+ * can contain one or more screens (based on the window size). Navigation from one screen to the
+ * next within a single destination will be handled directly in composables.
+ */
+data class TopLevelDestination(
+ override val route: String,
+ override val destination: String,
+ val selectedIcon: Icon,
+ val unselectedIcon: Icon,
+ val iconTextId: Int
+) : NiaNavigationDestination
diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
index 28f02a410..29fc62b67 100644
--- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
+++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
@@ -33,12 +33,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
-import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.material3.windowsizeclass.WindowSizeClass
-import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -46,11 +42,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
-import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
-import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.rememberNavController
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBar
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBarItem
@@ -59,10 +52,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavig
import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.DrawableResourceIcon
import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVectorIcon
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
-import com.google.samples.apps.nowinandroid.core.ui.JankMetricDisposableEffect
import com.google.samples.apps.nowinandroid.navigation.NiaNavHost
-import com.google.samples.apps.nowinandroid.navigation.NiaTopLevelNavigation
-import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_DESTINATIONS
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
@OptIn(
@@ -71,16 +61,11 @@ import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
ExperimentalComposeUiApi::class
)
@Composable
-fun NiaApp(windowSizeClass: WindowSizeClass) {
+fun NiaApp(
+ windowSizeClass: WindowSizeClass,
+ appState: NiaAppState = rememberNiaAppState(windowSizeClass)
+) {
NiaTheme {
- val navController = rememberNavController()
- val niaTopLevelNavigation = remember(navController) {
- NiaTopLevelNavigation(navController)
- }
-
- val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentDestination = navBackStackEntry?.destination
-
NiaBackground {
Scaffold(
modifier = Modifier.semantics {
@@ -89,12 +74,11 @@ fun NiaApp(windowSizeClass: WindowSizeClass) {
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
bottomBar = {
- if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
- windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
- ) {
+ if (appState.shouldShowBottomBar) {
NiaBottomBar(
- onNavigateToTopLevelDestination = niaTopLevelNavigation::navigateTo,
- currentDestination = currentDestination
+ destinations = appState.topLevelDestinations,
+ onNavigateToDestination = appState::navigate,
+ currentDestination = appState.currentDestination
)
}
}
@@ -108,19 +92,20 @@ fun NiaApp(windowSizeClass: WindowSizeClass) {
)
)
) {
- if (windowSizeClass.widthSizeClass != WindowWidthSizeClass.Compact &&
- windowSizeClass.heightSizeClass != WindowHeightSizeClass.Compact
- ) {
+ if (appState.shouldShowNavRail) {
NiaNavRail(
- onNavigateToTopLevelDestination = niaTopLevelNavigation::navigateTo,
- currentDestination = currentDestination,
+ destinations = appState.topLevelDestinations,
+ onNavigateToDestination = appState::navigate,
+ currentDestination = appState.currentDestination,
modifier = Modifier.safeDrawingPadding()
)
}
NiaNavHost(
- windowSizeClass = windowSizeClass,
- navController = navController,
+ navController = appState.navController,
+ onBackClick = appState::onBackClick,
+ onNavigateToDestination = appState::navigate,
+ windowSizeClass = appState.windowSizeClass,
modifier = Modifier
.padding(padding)
.consumedWindowInsets(padding)
@@ -128,33 +113,23 @@ fun NiaApp(windowSizeClass: WindowSizeClass) {
}
}
}
- JankMetricDisposableEffect(navController) { metricsHolder ->
- val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
- metricsHolder.state?.addState("Navigation", destination.route.toString())
- }
-
- navController.addOnDestinationChangedListener(listener)
-
- onDispose {
- navController.removeOnDestinationChangedListener(listener)
- }
- }
}
}
@Composable
private fun NiaNavRail(
- onNavigateToTopLevelDestination: (TopLevelDestination) -> Unit,
+ destinations: List,
+ onNavigateToDestination: (TopLevelDestination) -> Unit,
currentDestination: NavDestination?,
modifier: Modifier = Modifier,
) {
NiaNavigationRail(modifier = modifier) {
- TOP_LEVEL_DESTINATIONS.forEach { destination ->
+ destinations.forEach { destination ->
val selected =
currentDestination?.hierarchy?.any { it.route == destination.route } == true
NiaNavigationRailItem(
selected = selected,
- onClick = { onNavigateToTopLevelDestination(destination) },
+ onClick = { onNavigateToDestination(destination) },
icon = {
val icon = if (selected) {
destination.selectedIcon
@@ -180,7 +155,8 @@ private fun NiaNavRail(
@Composable
private fun NiaBottomBar(
- onNavigateToTopLevelDestination: (TopLevelDestination) -> Unit,
+ destinations: List,
+ onNavigateToDestination: (TopLevelDestination) -> Unit,
currentDestination: NavDestination?
) {
// Wrap the navigation bar in a surface so the color behind the system
@@ -193,13 +169,12 @@ private fun NiaBottomBar(
)
)
) {
-
- TOP_LEVEL_DESTINATIONS.forEach { destination ->
+ destinations.forEach { destination ->
val selected =
currentDestination?.hierarchy?.any { it.route == destination.route } == true
NiaNavigationBarItem(
selected = selected,
- onClick = { onNavigateToTopLevelDestination(destination) },
+ onClick = { onNavigateToDestination(destination) },
icon = {
val icon = if (selected) {
destination.selectedIcon
diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt
new file mode 100644
index 000000000..323c62dae
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2022 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.google.samples.apps.nowinandroid.ui
+
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
+import androidx.compose.material3.windowsizeclass.WindowSizeClass
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.navigation.NavController
+import androidx.navigation.NavDestination
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import androidx.tracing.trace
+import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.DrawableResourceIcon
+import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVectorIcon
+import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
+import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigationDestination
+import com.google.samples.apps.nowinandroid.core.ui.JankMetricDisposableEffect
+import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR
+import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination
+import com.google.samples.apps.nowinandroid.feature.interests.R as interestsR
+import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination
+import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
+
+@Composable
+fun rememberNiaAppState(
+ windowSizeClass: WindowSizeClass,
+ navController: NavHostController = rememberNavController()
+): NiaAppState {
+ NavigationTrackingSideEffect(navController)
+ return remember(navController, windowSizeClass) {
+ NiaAppState(navController, windowSizeClass)
+ }
+}
+
+class NiaAppState(
+ val navController: NavHostController,
+ val windowSizeClass: WindowSizeClass
+) {
+ val currentDestination: NavDestination?
+ @Composable get() = navController
+ .currentBackStackEntryAsState().value?.destination
+
+ val shouldShowBottomBar: Boolean
+ get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
+ windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
+
+ val shouldShowNavRail: Boolean
+ get() = !shouldShowBottomBar
+
+ val topLevelDestinations: List = listOf(
+ TopLevelDestination(
+ route = ForYouDestination.route,
+ destination = ForYouDestination.destination,
+ selectedIcon = DrawableResourceIcon(NiaIcons.Upcoming),
+ unselectedIcon = DrawableResourceIcon(NiaIcons.UpcomingBorder),
+ iconTextId = forYouR.string.for_you
+ ),
+ TopLevelDestination(
+ route = InterestsDestination.route,
+ destination = InterestsDestination.destination,
+ selectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
+ unselectedIcon = ImageVectorIcon(NiaIcons.Grid3x3),
+ iconTextId = interestsR.string.interests
+ )
+ )
+
+ // --------------------
+ // NAVIGATION LOGIC
+ // --------------------
+
+ fun navigate(destination: NiaNavigationDestination, route: String? = null) {
+ trace("Navigation: $destination") {
+ if (destination is TopLevelDestination) {
+ navController.navigate(route ?: destination.route) {
+ // Pop up to the start destination of the graph to
+ // avoid building up a large stack of destinations
+ // on the back stack as users select items
+ popUpTo(navController.graph.findStartDestination().id) {
+ saveState = true
+ }
+ // Avoid multiple copies of the same destination when
+ // reselecting the same item
+ launchSingleTop = true
+ // Restore state when reselecting a previously selected item
+ restoreState = true
+ }
+ } else {
+ navController.navigate(route ?: destination.route)
+ }
+ }
+ }
+
+ fun onBackClick() {
+ navController.popBackStack()
+ }
+}
+
+@Composable
+private fun NavigationTrackingSideEffect(navController: NavHostController) {
+ JankMetricDisposableEffect(navController) { metricsHolder ->
+ val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
+ metricsHolder.state?.addState("Navigation", destination.route.toString())
+ }
+
+ navController.addOnDestinationChangedListener(listener)
+
+ onDispose {
+ navController.removeOnDestinationChangedListener(listener)
+ }
+ }
+}
diff --git a/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt b/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt
index 983ef69cd..d3749335e 100644
--- a/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt
+++ b/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt
@@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.feature.author.navigation
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
@@ -24,20 +25,26 @@ import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigationDestina
import com.google.samples.apps.nowinandroid.feature.author.AuthorRoute
object AuthorDestination : NiaNavigationDestination {
- override val route = "author_route"
- override val destination = "author_destination"
const val authorIdArg = "authorId"
+ override val route = "author_route/{$authorIdArg}"
+ override val destination = "author_destination"
+
+ fun createNavigationRoute(authorIdArg: String): String {
+ return "author_route/$authorIdArg"
+ }
+
+ fun fromNavArgs(entry: NavBackStackEntry): String {
+ return entry.arguments?.getString(authorIdArg)!!
+ }
}
fun NavGraphBuilder.authorGraph(
onBackClick: () -> Unit
) {
composable(
- route = "${AuthorDestination.route}/{${AuthorDestination.authorIdArg}}",
+ route = AuthorDestination.route,
arguments = listOf(
- navArgument(AuthorDestination.authorIdArg) {
- type = NavType.StringType
- }
+ navArgument(AuthorDestination.authorIdArg) { type = NavType.StringType }
)
) {
AuthorRoute(onBackClick = onBackClick)
diff --git a/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt b/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt
index 39d3b0875..2368e713b 100644
--- a/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt
+++ b/feature-topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt
@@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.feature.topic.navigation
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
@@ -24,20 +25,26 @@ import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigationDestina
import com.google.samples.apps.nowinandroid.feature.topic.TopicRoute
object TopicDestination : NiaNavigationDestination {
- override val route = "topic_route"
- override val destination = "topic_destination"
const val topicIdArg = "topicId"
+ override val route = "topic_route/{$topicIdArg}"
+ override val destination = "topic_destination"
+
+ fun createNavigationRoute(topicIdArg: String): String {
+ return "topic_route/$topicIdArg"
+ }
+
+ fun fromNavArgs(entry: NavBackStackEntry): String {
+ return entry.arguments?.getString(topicIdArg)!!
+ }
}
fun NavGraphBuilder.topicGraph(
onBackClick: () -> Unit
) {
composable(
- route = "${TopicDestination.route}/{${TopicDestination.topicIdArg}}",
+ route = TopicDestination.route,
arguments = listOf(
- navArgument(TopicDestination.topicIdArg) {
- type = NavType.StringType
- }
+ navArgument(TopicDestination.topicIdArg) { type = NavType.StringType }
)
) {
TopicRoute(onBackClick = onBackClick)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index acd76bb9c..c9ac166fc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -75,6 +75,7 @@ androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navig
androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
androidx-metrics = { group = "androidx.metrics", name = "metrics-performance", version.ref = "androidxMetrics" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" }
+androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" }
androidx-savedstate-ktx = { group = "androidx.savedstate", name = "savedstate-ktx", version.ref= "androidxSavedState"}
androidx-startup = { group = "androidx.startup", name = "startup-runtime", version.ref = "androidxStartup" }