From 89163b5ea11d6df2f1471f3663e4fb0135e0168d Mon Sep 17 00:00:00 2001 From: Don Turner Date: Fri, 3 May 2024 15:37:35 +0100 Subject: [PATCH] Migrate remainder of app to type-safe navigation Change-Id: I02a8efb46695b3a90701966bfea4ed76aeec131b --- .../nowinandroid/navigation/NiaNavHost.kt | 5 +-- .../navigation/TopLevelDestination.kt | 8 ++++ .../samples/apps/nowinandroid/ui/NiaApp.kt | 3 +- .../apps/nowinandroid/ui/NiaAppState.kt | 19 +++++---- .../InterestsListDetailScreen.kt | 4 -- .../navigation/BookmarksNavigation.kt | 9 +++-- .../foryou/navigation/ForYouNavigation.kt | 15 +++---- .../navigation/InterestsNavigation.kt | 39 +------------------ .../interests/InterestsViewModelTest.kt | 3 +- .../topic/navigation/TopicNavigation.kt | 6 --- .../feature/topic/TopicViewModelTest.kt | 4 +- 11 files changed, 40 insertions(+), 75 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt index 39bc03de7..83bf544fd 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen -import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE +import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouScreen import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen @@ -40,12 +40,11 @@ fun NiaNavHost( appState: NiaAppState, onShowSnackbar: suspend (String, String?) -> Boolean, modifier: Modifier = Modifier, - startDestination: String = FOR_YOU_ROUTE, ) { val navController = appState.navController NavHost( navController = navController, - startDestination = startDestination, + startDestination = ForYouDestination::class, modifier = modifier, ) { forYouScreen(onTopicClick = navController::navigateToInterests) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt index aca7d54ab..c8af595b8 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt @@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.navigation import androidx.compose.ui.graphics.vector.ImageVector import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons +import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksDestination +import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination +import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination +import kotlin.reflect.KClass import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR import com.google.samples.apps.nowinandroid.feature.search.R as searchR @@ -33,23 +37,27 @@ enum class TopLevelDestination( val unselectedIcon: ImageVector, val iconTextId: Int, val titleTextId: Int, + val route: KClass<*>, ) { FOR_YOU( selectedIcon = NiaIcons.Upcoming, unselectedIcon = NiaIcons.UpcomingBorder, iconTextId = forYouR.string.feature_foryou_title, titleTextId = R.string.app_name, + route = ForYouDestination::class, ), BOOKMARKS( selectedIcon = NiaIcons.Bookmarks, unselectedIcon = NiaIcons.BookmarksBorder, iconTextId = bookmarksR.string.feature_bookmarks_title, titleTextId = bookmarksR.string.feature_bookmarks_title, + route = BookmarksDestination::class, ), INTERESTS( selectedIcon = NiaIcons.Grid3x3, unselectedIcon = NiaIcons.Grid3x3, iconTextId = searchR.string.feature_search_interests, titleTextId = searchR.string.feature_search_interests, + route = InterestsDestination::class, ), } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 8cbabc247..603a4b3c5 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground @@ -315,5 +316,5 @@ private fun Modifier.notificationDot(): Modifier = private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: TopLevelDestination) = this?.hierarchy?.any { - it.route?.contains(destination.name, true) ?: false + it.hasRoute(destination.route) } ?: false diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index b653d8910..40011b6d4 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState @@ -34,11 +35,11 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank -import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BOOKMARKS_ROUTE +import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksDestination import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigateToBookmarks -import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE +import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination import com.google.samples.apps.nowinandroid.feature.foryou.navigation.navigateToForYou -import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE +import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests import com.google.samples.apps.nowinandroid.feature.search.navigation.navigateToSearch import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination @@ -96,11 +97,13 @@ class NiaAppState( .currentBackStackEntryAsState().value?.destination val currentTopLevelDestination: TopLevelDestination? - @Composable get() = when (currentDestination?.route) { - FOR_YOU_ROUTE -> FOR_YOU - BOOKMARKS_ROUTE -> BOOKMARKS - INTERESTS_ROUTE -> INTERESTS - else -> null + @Composable get(){ + with(currentDestination) { + if (this?.hasRoute() == true) return FOR_YOU + if (this?.hasRoute() == true) return BOOKMARKS + if (this?.hasRoute() == true) return INTERESTS + } + return null } val shouldShowBottomBar: Boolean 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 f899164f8..397c947ef 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 @@ -29,15 +29,11 @@ import androidx.compose.runtime.getValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute -import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination -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.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt index 13d0baef0..f717a76f1 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt @@ -21,16 +21,19 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute +import kotlinx.serialization.Serializable -const val BOOKMARKS_ROUTE = "bookmarks_route" +@Serializable object BookmarksDestination -fun NavController.navigateToBookmarks(navOptions: NavOptions) = navigate(BOOKMARKS_ROUTE, navOptions) +fun NavController.navigateToBookmarks(navOptions: NavOptions) = + navigate(route = BookmarksDestination, navOptions) fun NavGraphBuilder.bookmarksScreen( onTopicClick: (String) -> Unit, onShowSnackbar: suspend (String, String?) -> Boolean, ) { - composable(route = BOOKMARKS_ROUTE) { + composable { BookmarksRoute(onTopicClick, onShowSnackbar) } } + diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index 8e94a491a..202921c33 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -19,28 +19,23 @@ package com.google.samples.apps.nowinandroid.feature.foryou.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions -import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navArgument import androidx.navigation.navDeepLink import com.google.samples.apps.nowinandroid.feature.foryou.ForYouRoute +import kotlinx.serialization.Serializable const val LINKED_NEWS_RESOURCE_ID = "linkedNewsResourceId" -const val FOR_YOU_ROUTE = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" private const val DEEP_LINK_URI_PATTERN = "https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}" +@Serializable data class ForYouDestination(val linkedNewsResourceId: String? = null) -fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(FOR_YOU_ROUTE, navOptions) +fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(route = ForYouDestination(), navOptions) fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { - composable( - route = FOR_YOU_ROUTE, + composable( deepLinks = listOf( navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN }, - ), - arguments = listOf( - navArgument(LINKED_NEWS_RESOURCE_ID) { type = NavType.StringType }, - ), + ) ) { ForYouRoute(onTopicClick) } diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt index e25c662c0..a1c267461 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt @@ -17,49 +17,14 @@ package com.google.samples.apps.nowinandroid.feature.interests.navigation import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions -import androidx.navigation.NavType -import androidx.navigation.compose.composable -import androidx.navigation.navArgument -import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute import kotlinx.serialization.Serializable + const val TOPIC_ID_ARG = "topicId" -const val INTERESTS_ROUTE_BASE = "interests_route" -const val INTERESTS_ROUTE = "$INTERESTS_ROUTE_BASE?$TOPIC_ID_ARG={$TOPIC_ID_ARG}" @Serializable data class InterestsDestination(val topicId: String?) -fun NavController.navigateToInterestsOld(topicId: String? = null, navOptions: NavOptions? = null) { - val route = if (topicId != null) { - "${INTERESTS_ROUTE_BASE}?${TOPIC_ID_ARG}=$topicId" - } else { - INTERESTS_ROUTE_BASE - } - navigate(route, navOptions) -} - fun NavController.navigateToInterests(topicId: String? = null, navOptions: NavOptions? = null) { navigate(route = InterestsDestination(topicId), navOptions) -} - - -/*fun NavGraphBuilder.interestsScreenOld( - onTopicClick: (String) -> Unit, -) { - composable( - route = INTERESTS_ROUTE, - arguments = listOf( - navArgument(TOPIC_ID_ARG) { - defaultValue = null - nullable = true - type = NavType.StringType - }, - ), - ) { - InterestsRoute(onTopicClick = onTopicClick) - } -}*/ - - +} \ No newline at end of file diff --git a/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt b/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt index 63d3c49b7..901c9b796 100644 --- a/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt +++ b/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt @@ -55,7 +55,8 @@ class InterestsViewModelTest { @Before fun setup() { viewModel = InterestsViewModel( - savedStateHandle = SavedStateHandle(mapOf(TOPIC_ID_ARG to testInputTopics[0].topic.id)), + //TODO: Figure out how to supply the correct dependency: InterestsDestination(topicId = testInputTopics[0].topic.id) + savedStateHandle = SavedStateHandle(mapOf("topicId" to testInputTopics[0].topic.id)), userDataRepository = userDataRepository, getFollowableTopics = getFollowableTopicsUseCase, ) 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 e1b2581f0..0cdfcb502 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 @@ -16,7 +16,6 @@ package com.google.samples.apps.nowinandroid.feature.topic.navigation -import androidx.annotation.VisibleForTesting import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptionsBuilder @@ -24,11 +23,6 @@ import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.topic.TopicScreen import kotlinx.serialization.Serializable -// TODO: Remove -@VisibleForTesting -internal const val TOPIC_ID_ARG = "topicId" -const val TOPIC_ROUTE = "topic_route" - @Serializable data class TopicDestination(val id: String) fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) { diff --git a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index 565732f59..4ebb6990d 100644 --- a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -25,7 +25,6 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepo import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule -import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ID_ARG import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @@ -60,7 +59,8 @@ class TopicViewModelTest { @Before fun setup() { viewModel = TopicViewModel( - savedStateHandle = SavedStateHandle(mapOf(TOPIC_ID_ARG to testInputTopics[0].topic.id)), + //TODO: Figure out how to supply the correct dependency TopicDestination(id = testInputTopics[0].topic.id) + savedStateHandle = SavedStateHandle(mapOf("id" to testInputTopics[0].topic.id)), userDataRepository = userDataRepository, topicsRepository = topicsRepository, userNewsResourceRepository = userNewsResourceRepository,