From 4f0c2e7b92d8399602e3f9fbc596235c4efccecc Mon Sep 17 00:00:00 2001 From: lihenggui Date: Tue, 27 Feb 2024 13:37:07 -0800 Subject: [PATCH] Implement multiplatform settings --- .../apps/nowinandroid/KotlinMultiplatform.kt | 8 +- .../core/database/DriverModule.kt | 1 - .../core/database/DatabaseModule.kt | 1 - core/datastore-proto/build.gradle.kts | 13 +- .../core/datastore/DarkThemeConfigProto.kt | 24 ++ .../core/datastore/ThemeBrandProto.kt | 23 ++ .../core/datastore/UserPreferences.kt | 53 +++ core/datastore/build.gradle.kts | 8 +- .../core/datastore/di/DataStoreComponent.kt | 37 --- .../core/datastore/di/SettingsComponent.kt | 36 -- .../src/commonMain/AndroidManifest.xml | 17 - .../core/datastore/IntToStringIdsMigration.kt | 50 --- .../core/datastore/ListToMapMigration.kt | 57 ---- .../datastore/NiaPreferencesDataSource.kt | 310 +++++++++--------- .../datastore/UserPreferencesSerializer.kt | 44 --- .../core/datastore/di/SettingsComponent.kt | 12 +- .../datastore/IntToStringIdsMigrationTest.kt | 86 ----- .../core/datastore/ListToMapMigrationTest.kt | 103 ------ .../datastore/NiaPreferencesDataSourceTest.kt | 22 +- .../UserPreferencesSerializerTest.kt | 64 ---- gradle/libs.versions.toml | 5 +- 21 files changed, 288 insertions(+), 686 deletions(-) create mode 100644 core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/DarkThemeConfigProto.kt create mode 100644 core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ThemeBrandProto.kt create mode 100644 core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferences.kt delete mode 100644 core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreComponent.kt delete mode 100644 core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt delete mode 100644 core/datastore/src/commonMain/AndroidManifest.xml delete mode 100644 core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt delete mode 100644 core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt delete mode 100644 core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializer.kt delete mode 100644 core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt delete mode 100644 core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt delete mode 100644 core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializerTest.kt diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt index 5f3fb97c0..f3bf46fc2 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinMultiplatform.kt @@ -53,7 +53,9 @@ internal fun Project.configureKotlinMultiplatform() { } // tier 1 - linuxX64() +// :core:datastore:linuxMain: Could not resolve com.russhwolf:multiplatform-settings-no-arg:1.1.1. +// https://github.com/russhwolf/multiplatform-settings/issues/113 +// linuxX64() macosX64() macosArm64() iosSimulatorArm64() @@ -82,8 +84,8 @@ internal fun Project.configureKotlinMultiplatform() { // linking fails for the linux test build if not built on a linux host // ensure the tests and linking for them is only done on linux hosts - project.tasks.named("linuxX64Test") { enabled = HostManager.hostIsLinux } - project.tasks.named("linkDebugTestLinuxX64") { enabled = HostManager.hostIsLinux } +// project.tasks.named("linuxX64Test") { enabled = HostManager.hostIsLinux } +// project.tasks.named("linkDebugTestLinuxX64") { enabled = HostManager.hostIsLinux } // Suppress 'expect'/'actual' classes are in Beta. targets.configureEach { diff --git a/core/database/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DriverModule.kt b/core/database/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DriverModule.kt index e5eb5090b..eb2c9ec5a 100644 --- a/core/database/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DriverModule.kt +++ b/core/database/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DriverModule.kt @@ -24,7 +24,6 @@ import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlSchema import app.cash.sqldelight.driver.android.AndroidSqliteDriver import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Provides @Component diff --git a/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt b/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt index 7fec4df6f..ba69cfc03 100644 --- a/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt +++ b/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.nowinandroid.core.database.dao.RecentSearchQueryD import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao import com.google.samples.apps.nowinandroid.core.di.IODispatcher -import kotlinx.coroutines.Dispatchers import me.tatarka.inject.annotations.Component import me.tatarka.inject.annotations.Provides diff --git a/core/datastore-proto/build.gradle.kts b/core/datastore-proto/build.gradle.kts index b39c544a6..53f2d19f6 100644 --- a/core/datastore-proto/build.gradle.kts +++ b/core/datastore-proto/build.gradle.kts @@ -17,6 +17,7 @@ plugins { alias(libs.plugins.nowinandroid.kmp.library) alias(libs.plugins.protobuf) + id("kotlinx-serialization") } android { @@ -31,9 +32,6 @@ protobuf { generateProtoTasks { all().forEach { task -> task.builtins { - register("java") { - option("lite") - } register("kotlin") { option("lite") } @@ -42,18 +40,11 @@ protobuf { } } -androidComponents.beforeVariants { - android.sourceSets.register(it.name) { - val buildDir = layout.buildDirectory.get().asFile - java.srcDir(buildDir.resolve("generated/source/proto/${it.name}/java")) - kotlin.srcDir(buildDir.resolve("generated/source/proto/${it.name}/kotlin")) - } -} - kotlin { sourceSets { commonMain.dependencies { api(libs.protobuf.kotlin.lite) + implementation(libs.kotlinx.serialization.core) } } } diff --git a/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/DarkThemeConfigProto.kt b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/DarkThemeConfigProto.kt new file mode 100644 index 000000000..38c047828 --- /dev/null +++ b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/DarkThemeConfigProto.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2024 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.datastore + +enum class DarkThemeConfigProto { + DARK_THEME_CONFIG_UNSPECIFIED, + DARK_THEME_CONFIG_FOLLOW_SYSTEM, + DARK_THEME_CONFIG_LIGHT, + DARK_THEME_CONFIG_DARK, +} diff --git a/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ThemeBrandProto.kt b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ThemeBrandProto.kt new file mode 100644 index 000000000..1b10258ff --- /dev/null +++ b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ThemeBrandProto.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 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.datastore + +enum class ThemeBrandProto { + THEME_BRAND_UNSPECIFIED, + THEME_BRAND_DEFAULT, + THEME_BRAND_ANDROID, +} diff --git a/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferences.kt b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferences.kt new file mode 100644 index 000000000..f8168fc0c --- /dev/null +++ b/core/datastore-proto/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferences.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 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.datastore + +import com.google.samples.apps.nowinandroid.core.datastore.DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM +import com.google.samples.apps.nowinandroid.core.datastore.ThemeBrandProto.THEME_BRAND_UNSPECIFIED +import kotlinx.serialization.Serializable + +// A lot of workaround brought by Proto +@Serializable +data class UserPreferences( + val topicChangeListVersion: Int, + val authorChangeListVersion: Int, + val newsResourceChangeListVersion: Int, + val hasDoneIntToStringIdMigration: Boolean, + val hasDoneListToMapMigration: Boolean, + val followedTopicIds: Set = emptySet(), + val followedAuthorIds: Set = emptySet(), + val bookmarkedNewsResourceIds: Set = emptySet(), + val viewedNewsResourceIds: Set = emptySet(), + val themeBrand: ThemeBrandProto, + val darkThemeConfig: DarkThemeConfigProto, + val shouldHideOnboarding: Boolean, + val useDynamicColor: Boolean, +) { + companion object { + val DEFAULT = UserPreferences( + topicChangeListVersion = 0, + authorChangeListVersion = 0, + newsResourceChangeListVersion = 0, + hasDoneIntToStringIdMigration = false, + hasDoneListToMapMigration = false, + themeBrand = THEME_BRAND_UNSPECIFIED, + darkThemeConfig = DARK_THEME_CONFIG_FOLLOW_SYSTEM, + shouldHideOnboarding = false, + useDynamicColor = false, + ) + } +} diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts index c6cf6bdac..883b16025 100644 --- a/core/datastore/build.gradle.kts +++ b/core/datastore/build.gradle.kts @@ -39,17 +39,15 @@ kotlin { implementation(libs.multiplatform.settings.serialization) implementation(libs.multiplatform.settings.coroutines) implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization.core) implementation(projects.core.model) implementation(projects.core.common) + implementation(projects.core.datastoreProto) } commonTest.dependencies { implementation(libs.kotlin.test) implementation(libs.kotlinx.coroutines.test) - } - androidMain.dependencies { - implementation(libs.androidx.datastore.core) - implementation(libs.androidx.datastore.preferences) - implementation(libs.multiplatform.settings.datastore) + implementation(libs.multiplatform.settings.test) } } } diff --git a/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreComponent.kt b/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreComponent.kt deleted file mode 100644 index 6ac6bcd41..000000000 --- a/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreComponent.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 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.datastore.di - -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.preferencesDataStore -import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -@Component -abstract class DataStoreComponent { - - private val Context.dataStore by preferencesDataStore("user_preferences") - - @Provides - fun providesDataStore(context: Context): DataStore { - return context.dataStore - } -} \ No newline at end of file diff --git a/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt b/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt deleted file mode 100644 index 0ef09c647..000000000 --- a/core/datastore/src/androidMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2024 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.datastore.di - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import com.russhwolf.settings.ExperimentalSettingsApi -import com.russhwolf.settings.coroutines.FlowSettings -import com.russhwolf.settings.datastore.DataStoreSettings -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -@Component -actual abstract class SettingsComponent { - @OptIn(ExperimentalSettingsApi::class) - @Provides - actual fun providesFlowSettings( - dataStore: DataStore - ): FlowSettings { - return DataStoreSettings() - } -} \ No newline at end of file diff --git a/core/datastore/src/commonMain/AndroidManifest.xml b/core/datastore/src/commonMain/AndroidManifest.xml deleted file mode 100644 index 51d0cfc2e..000000000 --- a/core/datastore/src/commonMain/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt deleted file mode 100644 index ef9c1dd03..000000000 --- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt +++ /dev/null @@ -1,50 +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.datastore - -import androidx.datastore.core.DataMigration - -/** - * Migrates saved ids from [Int] to [String] types - */ -internal object IntToStringIdsMigration : DataMigration { - - override suspend fun cleanUp() = Unit - - override suspend fun migrate(currentData: UserPreferences): UserPreferences = - currentData.copy { - // Migrate topic ids - deprecatedFollowedTopicIds.clear() - deprecatedFollowedTopicIds.addAll( - currentData.deprecatedIntFollowedTopicIdsList.map(Int::toString), - ) - deprecatedIntFollowedTopicIds.clear() - - // Migrate author ids - deprecatedFollowedAuthorIds.clear() - deprecatedFollowedAuthorIds.addAll( - currentData.deprecatedIntFollowedAuthorIdsList.map(Int::toString), - ) - deprecatedIntFollowedAuthorIds.clear() - - // Mark migration as complete - hasDoneIntToStringIdMigration = true - } - - override suspend fun shouldMigrate(currentData: UserPreferences): Boolean = - !currentData.hasDoneIntToStringIdMigration -} diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt deleted file mode 100644 index 5675aee05..000000000 --- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt +++ /dev/null @@ -1,57 +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.datastore - -import androidx.datastore.core.DataMigration - -/** - * Migrates from using lists to maps for user data. - */ -internal object ListToMapMigration : DataMigration { - - override suspend fun cleanUp() = Unit - - override suspend fun migrate(currentData: UserPreferences): UserPreferences = - currentData.copy { - // Migrate topic id lists - followedTopicIds.clear() - followedTopicIds.putAll( - currentData.deprecatedFollowedTopicIdsList.associateWith { true }, - ) - deprecatedFollowedTopicIds.clear() - - // Migrate author ids - followedAuthorIds.clear() - followedAuthorIds.putAll( - currentData.deprecatedFollowedAuthorIdsList.associateWith { true }, - ) - deprecatedFollowedAuthorIds.clear() - - // Migrate bookmarks - bookmarkedNewsResourceIds.clear() - bookmarkedNewsResourceIds.putAll( - currentData.deprecatedBookmarkedNewsResourceIdsList.associateWith { true }, - ) - deprecatedBookmarkedNewsResourceIds.clear() - - // Mark migration as complete - hasDoneListToMapMigration = true - } - - override suspend fun shouldMigrate(currentData: UserPreferences): Boolean = - !currentData.hasDoneListToMapMigration -} diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index e68e19468..078af8243 100644 --- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -16,193 +16,199 @@ package com.google.samples.apps.nowinandroid.core.datastore -import android.util.Log -import androidx.datastore.core.DataStore +import com.google.samples.apps.nowinandroid.core.di.IODispatcher import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand -import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.russhwolf.settings.ExperimentalSettingsApi -import com.russhwolf.settings.ObservableSettings import com.russhwolf.settings.Settings -import com.russhwolf.settings.coroutines.FlowSettings -import com.russhwolf.settings.coroutines.toFlowSettings -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.map -import java.io.IOException -import javax.inject.Inject - -@OptIn(ExperimentalSettingsApi::class) -class NiaPreferencesDataSource constructor( - private val settings: FlowSettings, +import com.russhwolf.settings.serialization.decodeValue +import com.russhwolf.settings.serialization.decodeValueOrNull +import com.russhwolf.settings.serialization.encodeValue +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi + +private const val USER_DATA_KEY = "userData" + +@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) +class NiaPreferencesDataSource( + private val settings: Settings, + private val dispatcher: IODispatcher, ) { + // FlowSettings did not support JS, use a workaround instead + // https://github.com/russhwolf/multiplatform-settings/issues/139 + val userData = MutableStateFlow( + settings.decodeValue( + key = USER_DATA_KEY, + serializer = UserPreferences.serializer(), + defaultValue = settings.decodeValueOrNull( + key = USER_DATA_KEY, + serializer = UserPreferences.serializer(), + ) ?: UserPreferences.DEFAULT, + ), + ) + + suspend fun setFollowedTopicIds(topicIds: Set) = withContext(dispatcher) { + val preference = settings.getUserPreference() + .copy(followedTopicIds = topicIds) + .updateShouldHideOnboardingIfNecessary() + settings.putUserPreference(preference) + userData.value = preference + } - val userData = userPreferences.data - .map { - UserData( - bookmarkedNewsResources = it.bookmarkedNewsResourceIdsMap.keys, - viewedNewsResources = it.viewedNewsResourceIdsMap.keys, - followedTopics = it.followedTopicIdsMap.keys, - themeBrand = when (it.themeBrand) { - null, - ThemeBrandProto.THEME_BRAND_UNSPECIFIED, - ThemeBrandProto.UNRECOGNIZED, - ThemeBrandProto.THEME_BRAND_DEFAULT, - -> ThemeBrand.DEFAULT - ThemeBrandProto.THEME_BRAND_ANDROID -> ThemeBrand.ANDROID - }, - darkThemeConfig = when (it.darkThemeConfig) { - null, - DarkThemeConfigProto.DARK_THEME_CONFIG_UNSPECIFIED, - DarkThemeConfigProto.UNRECOGNIZED, - DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM, - -> - DarkThemeConfig.FOLLOW_SYSTEM - DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> - DarkThemeConfig.LIGHT - DarkThemeConfigProto.DARK_THEME_CONFIG_DARK -> DarkThemeConfig.DARK + suspend fun setTopicIdFollowed(topicId: String, followed: Boolean) = withContext(dispatcher) { + val preference = settings.getUserPreference() + val newPreference = preference + .copy( + followedTopicIds = if (followed) { + preference.followedTopicIds + topicId + } else { + preference.followedTopicIds - topicId }, - useDynamicColor = it.useDynamicColor, - shouldHideOnboarding = it.shouldHideOnboarding, ) - } - - suspend fun setFollowedTopicIds(topicIds: Set) { - try { - userPreferences.updateData { - it.copy { - followedTopicIds.clear() - followedTopicIds.putAll(topicIds.associateWith { true }) - updateShouldHideOnboardingIfNecessary() - } - } - } catch (ioException: IOException) { - Log.e("NiaPreferences", "Failed to update user preferences", ioException) - } + .updateShouldHideOnboardingIfNecessary() + settings.putUserPreference(newPreference) + userData.value = newPreference } - suspend fun setTopicIdFollowed(topicId: String, followed: Boolean) { - try { - userPreferences.updateData { - it.copy { - if (followed) { - followedTopicIds.put(topicId, true) - } else { - followedTopicIds.remove(topicId) - } - updateShouldHideOnboardingIfNecessary() - } - } - } catch (ioException: IOException) { - Log.e("NiaPreferences", "Failed to update user preferences", ioException) - } + suspend fun setThemeBrand(themeBrand: ThemeBrand) = withContext(dispatcher) { + val newPreference = settings.getUserPreference() + .copy(themeBrand = themeBrand.toThemeBrandProto()) + settings.putUserPreference(newPreference) + userData.value = newPreference } - suspend fun setThemeBrand(themeBrand: ThemeBrand) { - userPreferences.updateData { - it.copy { - this.themeBrand = when (themeBrand) { - ThemeBrand.DEFAULT -> ThemeBrandProto.THEME_BRAND_DEFAULT - ThemeBrand.ANDROID -> ThemeBrandProto.THEME_BRAND_ANDROID - } - } - } + suspend fun setDynamicColorPreference(useDynamicColor: Boolean) = withContext(dispatcher) { + val newPreference = settings.getUserPreference() + .copy(useDynamicColor = useDynamicColor) + settings.putUserPreference(newPreference) + userData.value = newPreference } - suspend fun setDynamicColorPreference(useDynamicColor: Boolean) { - userPreferences.updateData { - it.copy { this.useDynamicColor = useDynamicColor } - } + suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) = withContext(dispatcher) { + val newPreference = settings.getUserPreference() + .copy(darkThemeConfig = darkThemeConfig.toDarkThemeConfigProto()) + settings.putUserPreference(newPreference) + userData.value = newPreference } - suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) { - userPreferences.updateData { - it.copy { - this.darkThemeConfig = when (darkThemeConfig) { - DarkThemeConfig.FOLLOW_SYSTEM -> - DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM - DarkThemeConfig.LIGHT -> DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT - DarkThemeConfig.DARK -> DarkThemeConfigProto.DARK_THEME_CONFIG_DARK - } - } - } - } - - suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) { - try { - userPreferences.updateData { - it.copy { - if (bookmarked) { - bookmarkedNewsResourceIds.put(newsResourceId, true) + suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) = + withContext(dispatcher) { + val preference = settings.getUserPreference() + val newPreferences = preference + .copy( + bookmarkedNewsResourceIds = if (bookmarked) { + preference.bookmarkedNewsResourceIds + newsResourceId } else { - bookmarkedNewsResourceIds.remove(newsResourceId) - } - } - } - } catch (ioException: IOException) { - Log.e("NiaPreferences", "Failed to update user preferences", ioException) + preference.bookmarkedNewsResourceIds - newsResourceId + }, + ) + settings.putUserPreference(newPreferences) + userData.value = newPreferences } - } suspend fun setNewsResourceViewed(newsResourceId: String, viewed: Boolean) { setNewsResourcesViewed(listOf(newsResourceId), viewed) } - suspend fun setNewsResourcesViewed(newsResourceIds: List, viewed: Boolean) { - userPreferences.updateData { prefs -> - prefs.copy { - newsResourceIds.forEach { id -> - if (viewed) { - viewedNewsResourceIds.put(id, true) + suspend fun setNewsResourcesViewed(newsResourceIds: List, viewed: Boolean) = + withContext(dispatcher) { + val preference = settings.getUserPreference() + val newPreferences = preference + .copy( + viewedNewsResourceIds = if (viewed) { + preference.viewedNewsResourceIds + newsResourceIds } else { - viewedNewsResourceIds.remove(id) - } - } - } + preference.viewedNewsResourceIds - newsResourceIds.toSet() + }, + ) + settings.putUserPreference(newPreferences) + userData.value = newPreferences } - } - suspend fun getChangeListVersions() = userPreferences.data - .map { - ChangeListVersions( - topicVersion = it.topicChangeListVersion, - newsResourceVersion = it.newsResourceChangeListVersion, - ) - } - .firstOrNull() ?: ChangeListVersions() + suspend fun getChangeListVersions(): ChangeListVersions = withContext(dispatcher) { + val preferences = settings.getUserPreference() + return@withContext ChangeListVersions( + topicVersion = preferences.topicChangeListVersion, + newsResourceVersion = preferences.newsResourceChangeListVersion, + ) + } /** * Update the [ChangeListVersions] using [update]. */ - suspend fun updateChangeListVersion(update: ChangeListVersions.() -> ChangeListVersions) { - try { - userPreferences.updateData { currentPreferences -> - val updatedChangeListVersions = update( - ChangeListVersions( - topicVersion = currentPreferences.topicChangeListVersion, - newsResourceVersion = currentPreferences.newsResourceChangeListVersion, - ), - ) - - currentPreferences.copy { - topicChangeListVersion = updatedChangeListVersions.topicVersion - newsResourceChangeListVersion = updatedChangeListVersions.newsResourceVersion - } - } - } catch (ioException: IOException) { - Log.e("NiaPreferences", "Failed to update user preferences", ioException) + suspend fun updateChangeListVersion(update: ChangeListVersions.() -> ChangeListVersions) = + withContext(dispatcher) { + val currentPreferences = settings.getUserPreference() + val updatedChangeListVersions = update( + ChangeListVersions( + topicVersion = currentPreferences.topicChangeListVersion, + newsResourceVersion = currentPreferences.newsResourceChangeListVersion, + ), + ) + val updatedPreference = currentPreferences.copy( + topicChangeListVersion = updatedChangeListVersions.topicVersion, + newsResourceChangeListVersion = updatedChangeListVersions.newsResourceVersion, + ) + settings.putUserPreference(updatedPreference) + userData.value = updatedPreference } + + suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) = withContext(dispatcher) { + val newPreference = settings.getUserPreference() + .copy(shouldHideOnboarding = shouldHideOnboarding) + settings.putUserPreference(newPreference) + userData.value = newPreference } +} - suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) { - userPreferences.updateData { - it.copy { this.shouldHideOnboarding = shouldHideOnboarding } - } +private fun UserPreferences.updateShouldHideOnboardingIfNecessary(): UserPreferences { + return if (followedTopicIds.isEmpty() && followedAuthorIds.isEmpty()) { + this.copy(shouldHideOnboarding = false) + } else { + this + } +} + +@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) +private fun Settings.putUserPreference(preference: UserPreferences) { + encodeValue( + key = USER_DATA_KEY, + serializer = UserPreferences.serializer(), + value = preference, + ) +} + +@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) +private fun Settings.getUserPreference(): UserPreferences { + return decodeValue( + key = USER_DATA_KEY, + serializer = UserPreferences.serializer(), + defaultValue = UserPreferences.DEFAULT, + ) +} + +fun ThemeBrandProto.toThemeBrand(): ThemeBrand { + return when (this) { + ThemeBrandProto.THEME_BRAND_UNSPECIFIED, + ThemeBrandProto.THEME_BRAND_DEFAULT, + -> ThemeBrand.DEFAULT + + ThemeBrandProto.THEME_BRAND_ANDROID -> ThemeBrand.ANDROID + } +} + +private fun ThemeBrand.toThemeBrandProto(): ThemeBrandProto { + return when (this) { + ThemeBrand.DEFAULT -> ThemeBrandProto.THEME_BRAND_DEFAULT + ThemeBrand.ANDROID -> ThemeBrandProto.THEME_BRAND_ANDROID } } -private fun UserPreferencesKt.Dsl.updateShouldHideOnboardingIfNecessary() { - if (followedTopicIds.isEmpty() && followedAuthorIds.isEmpty()) { - shouldHideOnboarding = false +private fun DarkThemeConfig.toDarkThemeConfigProto(): DarkThemeConfigProto { + return when (this) { + DarkThemeConfig.FOLLOW_SYSTEM -> DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM + DarkThemeConfig.DARK -> DarkThemeConfigProto.DARK_THEME_CONFIG_DARK + DarkThemeConfig.LIGHT -> DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT } } diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializer.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializer.kt deleted file mode 100644 index 40c1e210f..000000000 --- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializer.kt +++ /dev/null @@ -1,44 +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.datastore - -import androidx.datastore.core.CorruptionException -import androidx.datastore.core.Serializer -import com.google.protobuf.InvalidProtocolBufferException -import java.io.InputStream -import java.io.OutputStream -import javax.inject.Inject - -/** - * An [androidx.datastore.core.Serializer] for the [UserPreferences] proto. - */ -class UserPreferencesSerializer @Inject constructor() : Serializer { - override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance() - - override suspend fun readFrom(input: InputStream): UserPreferences = - try { - // readFrom is already called on the data store background thread - UserPreferences.parseFrom(input) - } catch (exception: InvalidProtocolBufferException) { - throw CorruptionException("Cannot read proto.", exception) - } - - override suspend fun writeTo(t: UserPreferences, output: OutputStream) { - // writeTo is already called on the data store background thread - t.writeTo(output) - } -} diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt index 86bca8793..a475d3e08 100644 --- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt +++ b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt @@ -16,12 +16,12 @@ package com.google.samples.apps.nowinandroid.core.datastore.di -import com.russhwolf.settings.ExperimentalSettingsApi -import com.russhwolf.settings.coroutines.FlowSettings +import com.russhwolf.settings.Settings +import me.tatarka.inject.annotations.Component import me.tatarka.inject.annotations.Provides -expect abstract class SettingsComponent { - @OptIn(ExperimentalSettingsApi::class) +@Component +abstract class SettingsComponent { @Provides - fun providesFlowSettings(): FlowSettings -} \ No newline at end of file + fun providesSettings(): Settings = Settings() +} diff --git a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt b/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt deleted file mode 100644 index 8b97cff34..000000000 --- a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt +++ /dev/null @@ -1,86 +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.datastore - -import kotlinx.coroutines.test.runTest -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -/** - * Unit test for [IntToStringIdsMigration] - */ -class IntToStringIdsMigrationTest { - - @Test - fun IntToStringIdsMigration_should_migrate_topic_ids() = runTest { - // Set up existing preferences with topic int ids - val preMigrationUserPreferences = userPreferences { - deprecatedIntFollowedTopicIds.addAll(listOf(1, 2, 3)) - } - // Assert that there are no string topic ids yet - assertEquals( - emptyList(), - preMigrationUserPreferences.deprecatedFollowedTopicIdsList, - ) - - // Run the migration - val postMigrationUserPreferences = - IntToStringIdsMigration.migrate(preMigrationUserPreferences) - - // Assert the deprecated int topic ids have been migrated to the string topic ids - assertEquals( - userPreferences { - deprecatedFollowedTopicIds.addAll(listOf("1", "2", "3")) - hasDoneIntToStringIdMigration = true - }, - postMigrationUserPreferences, - ) - - // Assert that the migration has been marked complete - assertTrue(postMigrationUserPreferences.hasDoneIntToStringIdMigration) - } - - @Test - fun IntToStringIdsMigration_should_migrate_author_ids() = runTest { - // Set up existing preferences with author int ids - val preMigrationUserPreferences = userPreferences { - deprecatedIntFollowedAuthorIds.addAll(listOf(4, 5, 6)) - } - // Assert that there are no string author ids yet - assertEquals( - emptyList(), - preMigrationUserPreferences.deprecatedFollowedAuthorIdsList, - ) - - // Run the migration - val postMigrationUserPreferences = - IntToStringIdsMigration.migrate(preMigrationUserPreferences) - - // Assert the deprecated int author ids have been migrated to the string author ids - assertEquals( - userPreferences { - deprecatedFollowedAuthorIds.addAll(listOf("4", "5", "6")) - hasDoneIntToStringIdMigration = true - }, - postMigrationUserPreferences, - ) - - // Assert that the migration has been marked complete - assertTrue(postMigrationUserPreferences.hasDoneIntToStringIdMigration) - } -} diff --git a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt b/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt deleted file mode 100644 index f7e083b45..000000000 --- a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt +++ /dev/null @@ -1,103 +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.datastore - -import kotlinx.coroutines.test.runTest -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class ListToMapMigrationTest { - - @Test - fun ListToMapMigration_should_migrate_topic_ids() = runTest { - // Set up existing preferences with topic ids - val preMigrationUserPreferences = userPreferences { - deprecatedFollowedTopicIds.addAll(listOf("1", "2", "3")) - } - // Assert that there are no topic ids in the map yet - assertEquals( - emptyMap(), - preMigrationUserPreferences.followedTopicIdsMap, - ) - - // Run the migration - val postMigrationUserPreferences = - ListToMapMigration.migrate(preMigrationUserPreferences) - - // Assert the deprecated topic ids have been migrated to the topic ids map - assertEquals( - mapOf("1" to true, "2" to true, "3" to true), - postMigrationUserPreferences.followedTopicIdsMap, - ) - - // Assert that the migration has been marked complete - assertTrue(postMigrationUserPreferences.hasDoneListToMapMigration) - } - - @Test - fun ListToMapMigration_should_migrate_author_ids() = runTest { - // Set up existing preferences with author ids - val preMigrationUserPreferences = userPreferences { - deprecatedFollowedAuthorIds.addAll(listOf("4", "5", "6")) - } - // Assert that there are no author ids in the map yet - assertEquals( - emptyMap(), - preMigrationUserPreferences.followedAuthorIdsMap, - ) - - // Run the migration - val postMigrationUserPreferences = - ListToMapMigration.migrate(preMigrationUserPreferences) - - // Assert the deprecated author ids have been migrated to the author ids map - assertEquals( - mapOf("4" to true, "5" to true, "6" to true), - postMigrationUserPreferences.followedAuthorIdsMap, - ) - - // Assert that the migration has been marked complete - assertTrue(postMigrationUserPreferences.hasDoneListToMapMigration) - } - - @Test - fun ListToMapMigration_should_migrate_bookmarks() = runTest { - // Set up existing preferences with bookmarks - val preMigrationUserPreferences = userPreferences { - deprecatedBookmarkedNewsResourceIds.addAll(listOf("7", "8", "9")) - } - // Assert that there are no bookmarks in the map yet - assertEquals( - emptyMap(), - preMigrationUserPreferences.bookmarkedNewsResourceIdsMap, - ) - - // Run the migration - val postMigrationUserPreferences = - ListToMapMigration.migrate(preMigrationUserPreferences) - - // Assert the deprecated bookmarks have been migrated to the bookmarks map - assertEquals( - mapOf("7" to true, "8" to true, "9" to true), - postMigrationUserPreferences.bookmarkedNewsResourceIdsMap, - ) - - // Assert that the migration has been marked complete - assertTrue(postMigrationUserPreferences.hasDoneListToMapMigration) - } -} diff --git a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt b/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt index 433812808..2ed30784f 100644 --- a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt +++ b/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt @@ -16,31 +16,30 @@ package com.google.samples.apps.nowinandroid.core.datastore -import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore +import com.russhwolf.settings.MapSettings +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder +import kotlin.test.BeforeTest +import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue class NiaPreferencesDataSourceTest { + @OptIn(ExperimentalCoroutinesApi::class) private val testScope = TestScope(UnconfinedTestDispatcher()) private lateinit var subject: NiaPreferencesDataSource - @get:Rule - val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build() - - @Before + @BeforeTest fun setup() { subject = NiaPreferencesDataSource( - tmpFolder.testUserPreferencesDataStore(testScope), + settings = MapSettings(), + dispatcher = Dispatchers.Unconfined, ) } @@ -84,7 +83,8 @@ class NiaPreferencesDataSourceTest { } @Test - fun shouldUseDynamicColorFalseByDefault() = testScope.runTest { + fun shouldUseDynamicColorFalseWhenSet() = testScope.runTest { + subject.setDynamicColorPreference(false) assertFalse(subject.userData.first().useDynamicColor) } diff --git a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializerTest.kt b/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializerTest.kt deleted file mode 100644 index ad7664fe5..000000000 --- a/core/datastore/src/commonTest/kotlin/com/google/samples/apps/nowinandroid/core/datastore/UserPreferencesSerializerTest.kt +++ /dev/null @@ -1,64 +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.datastore - -import androidx.datastore.core.CorruptionException -import kotlinx.coroutines.test.runTest -import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import kotlin.test.assertEquals - -class UserPreferencesSerializerTest { - private val userPreferencesSerializer = UserPreferencesSerializer() - - @Test - fun defaultUserPreferences_isEmpty() { - assertEquals( - userPreferences { - // Default value - }, - userPreferencesSerializer.defaultValue, - ) - } - - @Test - fun writingAndReadingUserPreferences_outputsCorrectValue() = runTest { - val expectedUserPreferences = userPreferences { - followedTopicIds.put("0", true) - followedTopicIds.put("1", true) - } - - val outputStream = ByteArrayOutputStream() - - expectedUserPreferences.writeTo(outputStream) - - val inputStream = ByteArrayInputStream(outputStream.toByteArray()) - - val actualUserPreferences = userPreferencesSerializer.readFrom(inputStream) - - assertEquals( - expectedUserPreferences, - actualUserPreferences, - ) - } - - @Test(expected = CorruptionException::class) - fun readingInvalidUserPreferences_throwsCorruptionException() = runTest { - userPreferencesSerializer.readFrom(ByteArrayInputStream(byteArrayOf(0))) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 081621dab..955c57dd8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,6 +136,7 @@ kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } +kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "androidTools" } lint-checks = { group = "com.android.tools.lint", name = "lint-checks", version.ref = "androidTools" } @@ -170,8 +171,8 @@ sqldelight-coroutines-extensions = { group = "app.cash.sqldelight", name = "coro sqldelight-primitive-adapters = { group = "app.cash.sqldelight", name = "primitive-adapters", version.ref = "sqldelight" } kotlin-inject-compiler-ksp = { group = "me.tatarka.inject", name = "kotlin-inject-compiler-ksp", version.ref = "kotlinInject" } kotlin-inject-runtime = { group = "me.tatarka.inject", name = "kotlin-inject-runtime", version.ref = "kotlinInject" } -multiplatform-settings = { group = "com.russhwolf", name = "multiplatform-settings", version.ref = "multiplatform-settings" } -multiplatform-settings-datastore = { group = "com.russhwolf", name = "multiplatform-settings-datastore", version.ref = "multiplatform-settings" } +multiplatform-settings = { group = "com.russhwolf", name = "multiplatform-settings-no-arg", version.ref = "multiplatform-settings" } +multiplatform-settings-test = { group = "com.russhwolf", name = "multiplatform-settings-test", version.ref = "multiplatform-settings" } multiplatform-settings-serialization = { group = "com.russhwolf", name = "multiplatform-settings-serialization", version.ref = "multiplatform-settings" } multiplatform-settings-coroutines = { group = "com.russhwolf", name = "multiplatform-settings-coroutines", version.ref = "multiplatform-settings" }