From d332f7b70d07d1f86d7571671bf1a1284080e0c4 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sun, 18 Jun 2023 11:23:17 +0200 Subject: [PATCH 1/5] Remove forced objenesis resolution strategy The linked issue is marked as resolved, and not able to reproduce the mentioned compilation error. --- app/build.gradle.kts | 2 -- .../src/main/kotlin/AndroidLibraryConventionPlugin.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c6e3eeb9..3783928f5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,7 +126,5 @@ dependencies { configurations.configureEach { resolutionStrategy { force(libs.junit4) - // Temporary workaround for https://issuetracker.google.com/174733673 - force("org.objenesis:objenesis:2.6") } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 287b09cf5..ca955d754 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -49,8 +49,6 @@ class AndroidLibraryConventionPlugin : Plugin { configurations.configureEach { resolutionStrategy { force(libs.findLibrary("junit4").get()) - // Temporary workaround for https://issuetracker.google.com/174733673 - force("org.objenesis:objenesis:2.6") } } dependencies { From 0a3069a0d82de1fde29ef3f8ee5726babdd11a5a Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sun, 18 Jun 2023 11:42:12 +0200 Subject: [PATCH 2/5] Remove forced junit resolution strategy By looking at the Gradle scan's dependency tab, junit 4.12 is already replaced by 4.13.2. There is no need to manually force this at the configuration level. --- app/build.gradle.kts | 7 ------- .../src/main/kotlin/AndroidLibraryConventionPlugin.kt | 6 ------ 2 files changed, 13 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3783928f5..1d8383b00 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -121,10 +121,3 @@ dependencies { implementation(libs.androidx.profileinstaller) implementation(libs.coil.kt) } - -// androidx.test is forcing JUnit, 4.12. This forces it to use 4.13 -configurations.configureEach { - resolutionStrategy { - force(libs.junit4) - } -} diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index ca955d754..864034561 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -21,7 +21,6 @@ import com.google.samples.apps.nowinandroid.configureGradleManagedDevices import com.google.samples.apps.nowinandroid.configureKotlinAndroid import com.google.samples.apps.nowinandroid.configurePrintApksTask import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests -import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure @@ -46,11 +45,6 @@ class AndroidLibraryConventionPlugin : Plugin { configurePrintApksTask(this) disableUnnecessaryAndroidTests(target) } - configurations.configureEach { - resolutionStrategy { - force(libs.findLibrary("junit4").get()) - } - } dependencies { add("androidTestImplementation", kotlin("test")) add("testImplementation", kotlin("test")) From b7799f0475603869cbc9eb973327abb41d1dc538 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 29 Jul 2023 13:46:30 +0200 Subject: [PATCH 3/5] Convert 'sealed' sub-object to 'data object' --- .../samples/apps/nowinandroid/MainActivityViewModel.kt | 2 +- .../google/samples/apps/nowinandroid/core/result/Result.kt | 2 +- .../google/samples/apps/nowinandroid/core/ui/NewsFeed.kt | 2 +- .../apps/nowinandroid/feature/foryou/OnboardingUiState.kt | 6 +++--- .../nowinandroid/feature/interests/InterestsViewModel.kt | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt index ebea4b965..09f4597a7 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -43,6 +43,6 @@ class MainActivityViewModel @Inject constructor( } sealed interface MainActivityUiState { - object Loading : MainActivityUiState + data object Loading : MainActivityUiState data class Success(val userData: UserData) : MainActivityUiState } diff --git a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/result/Result.kt b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/result/Result.kt index 4a06b8b9d..6ae12d634 100644 --- a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/result/Result.kt +++ b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/result/Result.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.onStart sealed interface Result { data class Success(val data: T) : Result data class Error(val exception: Throwable? = null) : Result - object Loading : Result + data object Loading : Result } fun Flow.asResult(): Flow> { diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index 16cd3edf7..4a9f5d7c9 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -112,7 +112,7 @@ sealed interface NewsFeedUiState { /** * The feed is still loading. */ - object Loading : NewsFeedUiState + data object Loading : NewsFeedUiState /** * The feed is loaded with the given list of news resources. diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt index 58f4f1683..70634b4c5 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt @@ -25,17 +25,17 @@ sealed interface OnboardingUiState { /** * The onboarding state is loading. */ - object Loading : OnboardingUiState + data object Loading : OnboardingUiState /** * The onboarding state was unable to load. */ - object LoadFailed : OnboardingUiState + data object LoadFailed : OnboardingUiState /** * There is no onboarding state. */ - object NotShown : OnboardingUiState + data object NotShown : OnboardingUiState /** * There is a onboarding state, with the given lists of topics. diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt index debc49bcd..c3d5ab6e8 100644 --- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt +++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt @@ -53,11 +53,11 @@ class InterestsViewModel @Inject constructor( } sealed interface InterestsUiState { - object Loading : InterestsUiState + data object Loading : InterestsUiState data class Interests( val topics: List, ) : InterestsUiState - object Empty : InterestsUiState + data object Empty : InterestsUiState } From e8449112b4c616d3ef9c6357c688d03d96d6a50d Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 29 Jul 2023 15:07:43 +0200 Subject: [PATCH 4/5] Replace StringDecoder injection with simpler API The initial reason for this `StringDecoder` was to make sure the AndroidX navigation route would not use incompatible chars in the resulting String. For that, `Uri.encode(String)` was used, but this is an Android API (`android.net.Uri`). Unit tests would fail because Uri is not mocked (since we are not using Robolectric). Therefore, a `StringDecoder` interface was introduced with 2 implementations: `UriDecoder` and `FakeStringDecoder`, Hilt modules were added, etc. FWIW, the naming of the API was misleading: `StringDecoder.decodeString(encodedString: String)` does not inform how encoded the input is, so `UriDecoder` could "decode" something that was not necessarily uri "encoded". The solution to this problem was to simply use regular Java APIs: - `URLDecoder.decode(urlEncodedTopicId, "UTF-8")` - `URLEncoder.encode(topicId, "UTF-8")` --- .../core/decoder/StringDecoder.kt | 21 ----------- .../nowinandroid/core/decoder/UriDecoder.kt | 24 ------------- .../core/decoder/di/StringDecoderModule.kt | 31 ---------------- .../core/testing/decoder/FakeStringDecoder.kt | 24 ------------- .../testing/di/TestStringDecoderModule.kt | 35 ------------------- .../feature/topic/TopicViewModel.kt | 4 +-- .../topic/navigation/TopicNavigation.kt | 10 +++--- .../feature/topic/TopicViewModelTest.kt | 2 -- 8 files changed, 6 insertions(+), 145 deletions(-) delete mode 100644 core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/StringDecoder.kt delete mode 100644 core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/UriDecoder.kt delete mode 100644 core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/di/StringDecoderModule.kt delete mode 100644 core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/decoder/FakeStringDecoder.kt delete mode 100644 core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestStringDecoderModule.kt diff --git a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/StringDecoder.kt b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/StringDecoder.kt deleted file mode 100644 index 29437cc71..000000000 --- a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/StringDecoder.kt +++ /dev/null @@ -1,21 +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.core.decoder - -interface StringDecoder { - fun decodeString(encodedString: String): String -} diff --git a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/UriDecoder.kt b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/UriDecoder.kt deleted file mode 100644 index b114bdab6..000000000 --- a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/UriDecoder.kt +++ /dev/null @@ -1,24 +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.core.decoder - -import android.net.Uri -import javax.inject.Inject - -class UriDecoder @Inject constructor() : StringDecoder { - override fun decodeString(encodedString: String): String = Uri.decode(encodedString) -} diff --git a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/di/StringDecoderModule.kt b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/di/StringDecoderModule.kt deleted file mode 100644 index e39b0e1f8..000000000 --- a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/decoder/di/StringDecoderModule.kt +++ /dev/null @@ -1,31 +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.core.decoder.di - -import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder -import com.google.samples.apps.nowinandroid.core.decoder.UriDecoder -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -abstract class StringDecoderModule { - @Binds - abstract fun bindStringDecoder(uriDecoder: UriDecoder): StringDecoder -} diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/decoder/FakeStringDecoder.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/decoder/FakeStringDecoder.kt deleted file mode 100644 index 7282661cc..000000000 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/decoder/FakeStringDecoder.kt +++ /dev/null @@ -1,24 +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.core.testing.decoder - -import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder -import javax.inject.Inject - -class FakeStringDecoder @Inject constructor() : StringDecoder { - override fun decodeString(encodedString: String): String = encodedString -} diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestStringDecoderModule.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestStringDecoderModule.kt deleted file mode 100644 index 0873ee546..000000000 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestStringDecoderModule.kt +++ /dev/null @@ -1,35 +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.core.testing.di - -import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder -import com.google.samples.apps.nowinandroid.core.decoder.di.StringDecoderModule -import com.google.samples.apps.nowinandroid.core.testing.decoder.FakeStringDecoder -import dagger.Binds -import dagger.Module -import dagger.hilt.components.SingletonComponent -import dagger.hilt.testing.TestInstallIn - -@Module -@TestInstallIn( - components = [SingletonComponent::class], - replaces = [StringDecoderModule::class], -) -abstract class TestStringDecoderModule { - @Binds - abstract fun bindsStringDecoder(fakeStringDecoder: FakeStringDecoder): StringDecoder -} diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index 2b2565f9e..7f8c6067e 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQue import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository -import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource @@ -43,13 +42,12 @@ import javax.inject.Inject @HiltViewModel class TopicViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - stringDecoder: StringDecoder, private val userDataRepository: UserDataRepository, topicsRepository: TopicsRepository, userNewsResourceRepository: UserNewsResourceRepository, ) : ViewModel() { - private val topicArgs: TopicArgs = TopicArgs(savedStateHandle, stringDecoder) + private val topicArgs: TopicArgs = TopicArgs(savedStateHandle) val topicId = topicArgs.topicId 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 0954a52ac..b012b12b1 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,7 +16,6 @@ package com.google.samples.apps.nowinandroid.feature.topic.navigation -import android.net.Uri import androidx.annotation.VisibleForTesting import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController @@ -24,19 +23,20 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder import com.google.samples.apps.nowinandroid.feature.topic.TopicRoute +import java.net.URLDecoder +import java.net.URLEncoder @VisibleForTesting internal const val topicIdArg = "topicId" internal class TopicArgs(val topicId: String) { - constructor(savedStateHandle: SavedStateHandle, stringDecoder: StringDecoder) : - this(stringDecoder.decodeString(checkNotNull(savedStateHandle[topicIdArg]))) + constructor(savedStateHandle: SavedStateHandle) : + this(URLDecoder.decode(checkNotNull(savedStateHandle[topicIdArg]), "UTF-8")) } fun NavController.navigateToTopic(topicId: String) { - val encodedId = Uri.encode(topicId) + val encodedId = URLEncoder.encode(topicId, "UTF-8") this.navigate("topic_route/$encodedId") { launchSingleTop = true } diff --git a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index ff7a88160..d365232d7 100644 --- a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -22,7 +22,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video import com.google.samples.apps.nowinandroid.core.model.data.Topic -import com.google.samples.apps.nowinandroid.core.testing.decoder.FakeStringDecoder import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository @@ -63,7 +62,6 @@ class TopicViewModelTest { fun setup() { viewModel = TopicViewModel( savedStateHandle = SavedStateHandle(mapOf(topicIdArg to testInputTopics[0].topic.id)), - stringDecoder = FakeStringDecoder(), userDataRepository = userDataRepository, topicsRepository = topicsRepository, userNewsResourceRepository = userNewsResourceRepository, From 0418997f5a049a41ac148112b5a9a825b9e166f7 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 10 Aug 2023 19:06:59 +0200 Subject: [PATCH 5/5] Extract `UTF-8` character encoding --- .../feature/topic/navigation/TopicNavigation.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 b012b12b1..c29b57d47 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 @@ -26,17 +26,20 @@ 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() @VisibleForTesting internal const val topicIdArg = "topicId" internal class TopicArgs(val topicId: String) { constructor(savedStateHandle: SavedStateHandle) : - this(URLDecoder.decode(checkNotNull(savedStateHandle[topicIdArg]), "UTF-8")) + this(URLDecoder.decode(checkNotNull(savedStateHandle[topicIdArg]), URL_CHARACTER_ENCODING)) } fun NavController.navigateToTopic(topicId: String) { - val encodedId = URLEncoder.encode(topicId, "UTF-8") + val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) this.navigate("topic_route/$encodedId") { launchSingleTop = true }