From 8d807ddb0621f951a1446c7f111a57937e329cb6 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 1 May 2024 13:46:54 +0100 Subject: [PATCH] Use SafeArgs navigation for Topic feature Change-Id: Idf4386f10c780d3edc1f8aa11b428cb146e982c3 --- .../kotlin/AndroidFeatureConventionPlugin.kt | 6 ++++ .../feature/topic/TopicViewModel.kt | 14 ++++++---- .../topic/navigation/TopicNavigation.kt | 28 +++++-------------- gradle/libs.versions.toml | 3 +- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 52c337521..a64cedd28 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -28,6 +28,10 @@ class AndroidFeatureConventionPlugin : Plugin { pluginManager.apply { apply("nowinandroid.android.library") apply("nowinandroid.android.hilt") + // Serialization is used for type-safe navigation. + // TODO: Use the plugin ID from the version catalog when + // https://github.com/gradle/gradle/issues/15383# is resolved + apply("org.jetbrains.kotlin.plugin.serialization") } extensions.configure { defaultConfig { @@ -45,7 +49,9 @@ class AndroidFeatureConventionPlugin : Plugin { add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) + add("implementation", libs.findLibrary("androidx.navigation.compose").get()) add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + add("implementation", libs.findLibrary("kotlinx.serialization.json").get()) add("androidTestImplementation", libs.findLibrary("androidx.lifecycle.runtimeTesting").get()) } diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index 255e40f8b..9dc30f9bb 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -28,7 +28,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.result.Result import com.google.samples.apps.nowinandroid.core.result.asResult -import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicArgs +import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicDestination import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -47,12 +47,14 @@ class TopicViewModel @Inject constructor( userNewsResourceRepository: UserNewsResourceRepository, ) : ViewModel() { - private val topicArgs: TopicArgs = TopicArgs(savedStateHandle) + // TODO: Remove when alpha08 is released + private val topicDestination = TopicDestination(savedStateHandle["id"]!!) + //private val topicDestination : TopicDestination = savedStateHandle.toRoute() - val topicId = topicArgs.topicId + val topicId = topicDestination.id val topicUiState: StateFlow = topicUiState( - topicId = topicArgs.topicId, + topicId = topicDestination.id, userDataRepository = userDataRepository, topicsRepository = topicsRepository, ) @@ -63,7 +65,7 @@ class TopicViewModel @Inject constructor( ) val newsUiState: StateFlow = newsUiState( - topicId = topicArgs.topicId, + topicId = topicDestination.id, userDataRepository = userDataRepository, userNewsResourceRepository = userNewsResourceRepository, ) @@ -75,7 +77,7 @@ class TopicViewModel @Inject constructor( fun followTopicToggle(followed: Boolean) { viewModelScope.launch { - userDataRepository.setTopicIdFollowed(topicArgs.topicId, followed) + userDataRepository.setTopicIdFollowed(topicDestination.id, followed) } } 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..af08bb58f 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 @@ -17,33 +17,23 @@ package com.google.samples.apps.nowinandroid.feature.topic.navigation import androidx.annotation.VisibleForTesting -import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptionsBuilder -import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navArgument import com.google.samples.apps.nowinandroid.feature.topic.TopicRoute -import java.net.URLDecoder -import java.net.URLEncoder -import kotlin.text.Charsets.UTF_8 - -private val URL_CHARACTER_ENCODING = UTF_8.name() +import kotlinx.serialization.Serializable +// TODO: Remove @VisibleForTesting internal const val TOPIC_ID_ARG = "topicId" const val TOPIC_ROUTE = "topic_route" -internal class TopicArgs(val topicId: String) { - constructor(savedStateHandle: SavedStateHandle) : - this(URLDecoder.decode(checkNotNull(savedStateHandle[TOPIC_ID_ARG]), URL_CHARACTER_ENCODING)) -} +@Serializable +data class TopicDestination(val id: 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(TopicDestination(topicId)) { navOptions() } } @@ -53,12 +43,7 @@ fun NavGraphBuilder.topicScreen( onBackClick: () -> Unit, onTopicClick: (String) -> Unit, ) { - composable( - route = "topic_route/{$TOPIC_ID_ARG}", - arguments = listOf( - navArgument(TOPIC_ID_ARG) { type = NavType.StringType }, - ), - ) { + composable { TopicRoute( showBackButton = showBackButton, onBackClick = onBackClick, @@ -66,3 +51,4 @@ fun NavGraphBuilder.topicScreen( ) } } + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c354432c..46ae30127 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,8 @@ androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.7.0" androidxMacroBenchmark = "1.2.2" androidxMetrics = "1.0.0-alpha04" -androidxNavigation = "2.7.4" +#androidxNavigation = "2.8.0-SNAPSHOT" +androidxNavigation = "2.8.0-alpha07" androidxProfileinstaller = "1.3.1" androidxTestCore = "1.5.0" androidxTestExt = "1.1.5"