From 00f251540e4866755289e07f76105313ca01aa12 Mon Sep 17 00:00:00 2001 From: Mustafa Ozhan Date: Fri, 13 Oct 2023 14:41:44 +0200 Subject: [PATCH] [Oztechan/CCC#2855] Add Flow support for multiplatform settings --- .../client-core-persistence.gradle.kts | 3 ++ .../di/ClientCorePersistenceModule.android.kt | 4 +- .../client/core/persistence/Persistence.kt | 3 ++ .../core/persistence/PersistenceImpl.kt | 25 ++++++++++- .../core/persistence/PersistenceTest.kt | 45 ++++++++++++++++++- .../di/ClientCorePersistenceModule.ios.kt | 4 +- gradle/libs.versions.toml | 1 + 7 files changed, 77 insertions(+), 8 deletions(-) diff --git a/client/core/persistence/client-core-persistence.gradle.kts b/client/core/persistence/client-core-persistence.gradle.kts index 1e6b6411eb..88abf04d5f 100644 --- a/client/core/persistence/client-core-persistence.gradle.kts +++ b/client/core/persistence/client-core-persistence.gradle.kts @@ -21,7 +21,9 @@ kotlin { dependencies { libs.common.apply { implementation(koinCore) + implementation(coroutines) implementation(multiplatformSettings) + implementation(multiplatformSettingsCoroutines) } } } @@ -30,6 +32,7 @@ kotlin { libs.common.apply { implementation(test) implementation(mockative) + implementation(coroutinesTest) } } } diff --git a/client/core/persistence/src/androidMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.android.kt b/client/core/persistence/src/androidMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.android.kt index 784241f725..d9e800f198 100644 --- a/client/core/persistence/src/androidMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.android.kt +++ b/client/core/persistence/src/androidMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.android.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.SharedPreferences import com.oztechan.ccc.client.core.persistence.Persistence import com.oztechan.ccc.client.core.persistence.PersistenceImpl -import com.russhwolf.settings.Settings +import com.russhwolf.settings.ObservableSettings import com.russhwolf.settings.SharedPreferencesSettings import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf @@ -14,7 +14,7 @@ private const val KEY_APPLICATION_PREFERENCES = "application_preferences" actual val clientCorePersistenceModule = module { singleOf(::provideSharedPreferences) - single { SharedPreferencesSettings(get()) } + single { SharedPreferencesSettings(get()) } singleOf(::PersistenceImpl) { bind() } } diff --git a/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/Persistence.kt b/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/Persistence.kt index 77002313a1..af3682d029 100644 --- a/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/Persistence.kt +++ b/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/Persistence.kt @@ -1,6 +1,9 @@ package com.oztechan.ccc.client.core.persistence +import kotlinx.coroutines.flow.Flow + interface Persistence { fun getValue(key: String, defaultValue: T): T fun setValue(key: String, value: T) + fun getFlow(key: String, defaultValue: T): Flow } diff --git a/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceImpl.kt b/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceImpl.kt index a2747caf61..b874635776 100644 --- a/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceImpl.kt +++ b/client/core/persistence/src/commonMain/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceImpl.kt @@ -1,9 +1,16 @@ package com.oztechan.ccc.client.core.persistence import com.oztechan.ccc.client.core.persistence.error.UnsupportedPersistenceException -import com.russhwolf.settings.Settings +import com.russhwolf.settings.ObservableSettings +import com.russhwolf.settings.coroutines.getBooleanFlow +import com.russhwolf.settings.coroutines.getFloatFlow +import com.russhwolf.settings.coroutines.getIntFlow +import com.russhwolf.settings.coroutines.getLongFlow +import com.russhwolf.settings.coroutines.getStringFlow +import kotlinx.coroutines.flow.Flow + +internal class PersistenceImpl(private val settings: ObservableSettings) : Persistence { -internal class PersistenceImpl(private val settings: Settings) : Persistence { @Suppress("UNCHECKED_CAST") override fun getValue(key: String, defaultValue: T): T = when (defaultValue) { is Long -> settings.getLong(key, defaultValue) as T @@ -22,4 +29,18 @@ internal class PersistenceImpl(private val settings: Settings) : Persistence { is Float -> settings.putFloat(key, value) else -> throw UnsupportedPersistenceException() } + + @Suppress("UNCHECKED_CAST", "OPT_IN_USAGE") + override fun getFlow(key: String, defaultValue: T): Flow = + when (defaultValue) { + is Long -> settings.getLongFlow(key, defaultValue) as Flow + is String -> settings.getStringFlow(key, defaultValue) as Flow + is Int -> settings.getIntFlow(key, defaultValue) as Flow + is Boolean -> settings.getBooleanFlow(key, defaultValue) as Flow + is Float -> settings.getFloatFlow(key, defaultValue) as Flow + else -> { + settings + throw UnsupportedPersistenceException() + } + } } diff --git a/client/core/persistence/src/commonTest/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceTest.kt b/client/core/persistence/src/commonTest/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceTest.kt index cacd3beb0b..a0bb9346b3 100644 --- a/client/core/persistence/src/commonTest/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceTest.kt +++ b/client/core/persistence/src/commonTest/kotlin/com/oztechan/ccc/client/core/persistence/PersistenceTest.kt @@ -1,13 +1,21 @@ package com.oztechan.ccc.client.core.persistence import com.oztechan.ccc.client.core.persistence.error.UnsupportedPersistenceException -import com.russhwolf.settings.Settings +import com.russhwolf.settings.ObservableSettings +import com.russhwolf.settings.coroutines.getBooleanFlow +import com.russhwolf.settings.coroutines.getFloatFlow +import com.russhwolf.settings.coroutines.getIntFlow +import com.russhwolf.settings.coroutines.getLongFlow +import com.russhwolf.settings.coroutines.getStringFlow import io.mockative.Mock import io.mockative.classOf import io.mockative.configure import io.mockative.every import io.mockative.mock import io.mockative.verify +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -19,7 +27,8 @@ internal class PersistenceTest { } @Mock - private val settings = configure(mock(classOf())) { stubsUnitByDefault = true } + private val settings = + configure(mock(classOf())) { stubsUnitByDefault = true } private val key = "key" @@ -60,6 +69,38 @@ internal class PersistenceTest { .wasInvoked() } + @Suppress("OPT_IN_USAGE") + @Test + fun `getFlow returns the same type`() = runTest { + every { settings.getFloatFlow(key, mockFloat) } + .returns(flowOf(mockFloat)) + every { settings.getBooleanFlow(key, mockBoolean) } + .returns(flowOf(mockBoolean)) + every { settings.getIntFlow(key, mockInt) } + .returns(flowOf(mockInt)) + every { settings.getStringFlow(key, mockString) } + .returns(flowOf(mockString)) + every { settings.getLongFlow(key, mockLong) } + .returns(flowOf(mockLong)) + + assertEquals(mockFloat, persistence.getFlow(key, mockFloat).firstOrNull()) + assertEquals(mockBoolean, persistence.getFlow(key, mockBoolean).firstOrNull()) + assertEquals(mockInt, persistence.getFlow(key, mockInt).firstOrNull()) + assertEquals(mockString, persistence.getFlow(key, mockString).firstOrNull()) + assertEquals(mockLong, persistence.getFlow(key, mockLong).firstOrNull()) + + verify { settings.getFloatFlow(key, mockFloat) } + .wasInvoked() + verify { settings.getBooleanFlow(key, mockBoolean) } + .wasInvoked() + verify { settings.getIntFlow(key, mockInt) } + .wasInvoked() + verify { settings.getStringFlow(key, mockString) } + .wasInvoked() + verify { settings.getLongFlow(key, mockLong) } + .wasInvoked() + } + @Test fun `setValue sets the same type`() { persistence.setValue(key, mockFloat) diff --git a/client/core/persistence/src/iosMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.ios.kt b/client/core/persistence/src/iosMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.ios.kt index 1e01c68c4a..64d4017acb 100644 --- a/client/core/persistence/src/iosMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.ios.kt +++ b/client/core/persistence/src/iosMain/kotlin/com/oztechan/ccc/client/core/persistence/di/ClientCorePersistenceModule.ios.kt @@ -3,13 +3,13 @@ package com.oztechan.ccc.client.core.persistence.di import com.oztechan.ccc.client.core.persistence.Persistence import com.oztechan.ccc.client.core.persistence.PersistenceImpl import com.russhwolf.settings.NSUserDefaultsSettings -import com.russhwolf.settings.Settings +import com.russhwolf.settings.ObservableSettings import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module actual val clientCorePersistenceModule = module { - single { + single { NSUserDefaultsSettings(get().userDefaults) } singleOf(::PersistenceImpl) { bind() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dacbb5dc8a..0084729480 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,7 @@ common-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl common-testAnnotations = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" } common-koinCore = { module = "io.insert-koin:koin-core", version.ref = "koinCore" } common-multiplatformSettings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatformSettings" } +common-multiplatformSettingsCoroutines = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "multiplatformSettings" } common-kotlinXDateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinXDateTime" } common-ktorLogging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } common-ktorJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }