From 55b3b677a0451816e560ace7025e9fcc6822fadb Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Mon, 12 May 2025 16:14:36 +0200 Subject: [PATCH 1/9] Use koin annotations in persistence module --- ...nventions-annotations-processor.gradle.kts | 6 ++- gradle/libs.versions.toml | 4 +- .../kmptemplate/app/injection/AppInjection.kt | 4 +- .../network/rest/plugin/UserAgentPlugin.kt | 3 +- shared/persistence/build.gradle.kts | 10 +++++ .../injection/PersistenceModule.android.kt | 8 ++++ .../injection/PersistencePlatformModule.kt | 12 ----- .../platform/PlatformComponent.android.kt | 12 +++++ .../injection/PersistenceModule.kt | 44 ++++++++++++++----- .../injection/PersistencePlatformModule.kt | 5 --- .../persistence/injection/Qualifiers.kt | 9 ++-- .../persistence/platform/PlatformComponent.kt | 9 ++++ .../injection/PersistenceModule.ios.kt | 8 ++++ .../PlatformComponent.ios.kt} | 14 +++--- 14 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt delete mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt create mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt delete mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt create mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt create mode 100644 shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt rename shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/{injection/PersistencePlatformModule.kt => platform/PlatformComponent.ios.kt} (64%) diff --git a/convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts b/convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts index cd758019..18ae0ebc 100644 --- a/convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts +++ b/convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts @@ -19,7 +19,7 @@ afterEvaluate { if (extension.useKoin) { ksp { // enable compile time check - arg("KOIN_CONFIG_CHECK", "false") + arg("KOIN_CONFIG_CHECK", "true") // disable default module generation arg("KOIN_DEFAULT_MODULE", "false") } @@ -32,6 +32,10 @@ afterEvaluate { dependencies { if (extension.useKoin) { add("kspCommonMainMetadata", libs.koin.ksp.compiler) + add("kspAndroid", libs.koin.ksp.compiler) + add("kspIosX64", libs.koin.ksp.compiler) + add("kspIosArm64", libs.koin.ksp.compiler) + add("kspIosSimulatorArm64", libs.koin.ksp.compiler) } // Enable source generation by KSP to commonMain only diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9eb23aa0..571e7a9c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ jetbrainsComposeRuntime = "1.7.0" # Needs to align with version in Compose BOM a androidxActivity = "1.9.2" decompose = "3.1.0" essenty = "2.1.0" -koin = "3.5.6" -koinAnnotations = "1.4.0" # Must be compatible with: `ksp` +koin = "4.0.2" +koinAnnotations = "2.0.1-RC1" # Must be compatible with: `ksp` kotlinx-coroutines = "1.8.1" kotlinx-immutableCollections = "0.3.8" kotlinx-dateTime = "0.6.1" diff --git a/shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt b/shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt index 63306aab..6b12403e 100644 --- a/shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt +++ b/shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt @@ -3,7 +3,7 @@ package app.futured.kmptemplate.app.injection import app.futured.kmptemplate.feature.injection.FeatureModule import app.futured.kmptemplate.network.graphql.injection.NetworkGraphqlModule import app.futured.kmptemplate.network.rest.injection.NetworkRestModule -import app.futured.kmptemplate.persistence.injection.persistenceModule +import app.futured.kmptemplate.persistence.injection.PersistenceModule import app.futured.kmptemplate.platform.binding.PlatformBindings import app.futured.kmptemplate.platform.injection.platformModule import org.koin.core.context.startKoin @@ -30,7 +30,7 @@ internal object AppInjection { FeatureModule().module, NetworkGraphqlModule().module, NetworkRestModule().module, - persistenceModule(), + PersistenceModule().module, ) } } diff --git a/shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt b/shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt index 9d0c43c8..685db8a8 100644 --- a/shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt +++ b/shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt @@ -4,10 +4,11 @@ import app.futured.kmptemplate.network.rest.FlavorConstants import app.futured.kmptemplate.platform.binding.Platform import io.ktor.client.HttpClientConfig import io.ktor.client.plugins.UserAgent +import org.koin.core.annotation.Provided import org.koin.core.annotation.Single @Single -internal class UserAgentPlugin(platform: Platform) : HttpClientPlugin { +internal class UserAgentPlugin(@Provided platform: Platform) : HttpClientPlugin { private val userAgentString = with(platform) { listOf( diff --git a/shared/persistence/build.gradle.kts b/shared/persistence/build.gradle.kts index 555211b4..b9d8acbe 100644 --- a/shared/persistence/build.gradle.kts +++ b/shared/persistence/build.gradle.kts @@ -7,6 +7,11 @@ plugins { alias(libs.plugins.kotlin.serialization) id(libs.plugins.conventions.lint.get().pluginId) + id(libs.plugins.conventions.annotationProcessing.get().pluginId) +} + +annotations { + useKoin = true } kotlin { @@ -26,6 +31,7 @@ kotlin { commonMain { dependencies { implementation(libs.koin.core) + api(libs.koin.annotations) implementation(libs.kotlinx.coroutines.core) implementation(libs.logging.kermit) implementation(libs.kotlinx.serialization.json) @@ -38,6 +44,10 @@ kotlin { implementation(libs.kotlin.test) } } + + /*sourceSets.named("commonMain").configure { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + }*/ } } diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt new file mode 100644 index 00000000..86f48a05 --- /dev/null +++ b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt @@ -0,0 +1,8 @@ +package app.futured.kmptemplate.persistence.injection + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan("app.futured.kmptemplate.persistence.platform") +actual class PersistencePlatformModule actual constructor() diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt deleted file mode 100644 index 886fde07..00000000 --- a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.futured.kmptemplate.persistence.injection - -import android.content.Context -import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME -import okio.Path.Companion.toPath -import org.koin.dsl.module - -internal actual fun persistencePlatformModule() = module { - single(Qualifiers.DataStorePath) { - get().filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() - } -} diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt new file mode 100644 index 00000000..3ef47519 --- /dev/null +++ b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt @@ -0,0 +1,12 @@ +package app.futured.kmptemplate.persistence.platform + +import android.content.Context +import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME +import okio.Path +import okio.Path.Companion.toPath +import org.koin.core.annotation.Factory + +@Factory +actual class PlatformComponent(val context: Context) { + actual fun getDatastorePath(): Path = context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() +} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt index dd34c68c..5d35bdc4 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt @@ -1,27 +1,47 @@ package app.futured.kmptemplate.persistence.injection +import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences import app.futured.kmptemplate.persistence.persistence.JsonPersistence import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence +import app.futured.kmptemplate.persistence.platform.PlatformComponent import kotlinx.serialization.json.Json +import okio.Path +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module +import org.koin.core.annotation.Single import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -fun persistenceModule() = module { - // expect/actual Koin module - includes(persistencePlatformModule()) +@Module(includes = [PersistencePlatformModule::class]) +@ComponentScan("app.futured.kmptemplate.persistence") +class PersistenceModule { - single { - PreferenceDataStoreFactory.createWithPath( - produceFile = { get(Qualifiers.DataStorePath) }, + @Single + internal fun provideDataStore(platformComponent: PlatformComponent): DataStore { + return PreferenceDataStoreFactory.createWithPath( + produceFile = { platformComponent.getDatastorePath() }, ) } - singleOf(::PrimitivePersistence) - single { JsonPersistence(get(), get(Qualifiers.PersistenceJson)) } + @Single + internal fun providePrimitivePersistence(dataStore: DataStore): PrimitivePersistence { + return PrimitivePersistence(dataStore) + } + + @Single + internal fun provideJsonPersistence( + dataStore: DataStore, + @PersistenceJson json: Json, + ): JsonPersistence { + return JsonPersistence(dataStore, json) + } - single(Qualifiers.PersistenceJson) { - Json { + @Single + @PersistenceJson + internal fun provideJson(): Json { + return Json { encodeDefaults = true isLenient = false ignoreUnknownKeys = true @@ -29,3 +49,7 @@ fun persistenceModule() = module { } } } + +@Module +@ComponentScan("app.futured.kmptemplate.persistence.platform") +expect class PersistencePlatformModule() diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt deleted file mode 100644 index 65440820..00000000 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt +++ /dev/null @@ -1,5 +0,0 @@ -package app.futured.kmptemplate.persistence.injection - -import org.koin.core.module.Module - -internal expect fun persistencePlatformModule(): Module diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt index f3261393..81c5f4d6 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt @@ -1,9 +1,6 @@ package app.futured.kmptemplate.persistence.injection -import org.koin.core.qualifier.named +import org.koin.core.annotation.Named -internal object Qualifiers { - - val PersistenceJson = named("PersistenceJson") - val DataStorePath = named("DataStoreFilePath") -} +@Named +annotation class PersistenceJson diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt new file mode 100644 index 00000000..1c0bc91d --- /dev/null +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt @@ -0,0 +1,9 @@ +package app.futured.kmptemplate.persistence.platform + +import okio.Path +import org.koin.core.annotation.Factory + +@Factory +expect class PlatformComponent { + fun getDatastorePath(): Path +} diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt new file mode 100644 index 00000000..86f48a05 --- /dev/null +++ b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt @@ -0,0 +1,8 @@ +package app.futured.kmptemplate.persistence.injection + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan("app.futured.kmptemplate.persistence.platform") +actual class PersistencePlatformModule actual constructor() diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt similarity index 64% rename from shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt rename to shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt index c28b6685..7e342d3c 100644 --- a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt +++ b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt @@ -1,17 +1,19 @@ -package app.futured.kmptemplate.persistence.injection +package app.futured.kmptemplate.persistence.platform import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME import kotlinx.cinterop.ExperimentalForeignApi +import okio.Path import okio.Path.Companion.toPath -import org.koin.dsl.module +import org.koin.core.annotation.Factory import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager import platform.Foundation.NSURL import platform.Foundation.NSUserDomainMask -@OptIn(ExperimentalForeignApi::class) -internal actual fun persistencePlatformModule() = module { - single(Qualifiers.DataStorePath) { +@Factory +actual class PlatformComponent { + @OptIn(ExperimentalForeignApi::class) + actual fun getDatastorePath(): Path { val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, @@ -20,6 +22,6 @@ internal actual fun persistencePlatformModule() = module { error = null, ) - (requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath() + return (requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath() } } From b01fc40df255c52b6cdb6deeef0b7c2e5fb86cd8 Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Mon, 16 Jun 2025 14:40:54 +0200 Subject: [PATCH 2/9] Remove ComponentScan --- gradle/libs.versions.toml | 12 ++++++------ .../platform/PlatformComponent.android.kt | 2 +- .../persistence/injection/PersistenceModule.kt | 4 ---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 571e7a9c..880c05b7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -agp = "8.9.0" -kotlin = "2.1.10" -ksp = "2.1.10-1.0.31" # Must be compatible with: `kotlin` +agp = "8.10.1" +kotlin = "2.1.20" +ksp = "2.1.20-1.0.32" # Must be compatible with: `kotlin` desugarLibs = "2.1.5" androidxLifecycle = "2.8.6" androidxComposeBom = "2024.10.00" @@ -9,8 +9,8 @@ jetbrainsComposeRuntime = "1.7.0" # Needs to align with version in Compose BOM a androidxActivity = "1.9.2" decompose = "3.1.0" essenty = "2.1.0" -koin = "4.0.2" -koinAnnotations = "2.0.1-RC1" # Must be compatible with: `ksp` +koin = "4.1.0" +koinAnnotations = "2.0.1" # Must be compatible with: `ksp` kotlinx-coroutines = "1.8.1" kotlinx-immutableCollections = "0.3.8" kotlinx-dateTime = "0.6.1" @@ -24,7 +24,7 @@ ktor = "3.1.0" # Must be compatible with: `ktorfit` kotlinx-serialization = "1.8.0" # Must be compatible with: `kotlin` timber = "5.0.1" kermit = "2.0.2" -skie = "0.10.1" # Must be compatible with: `kotlin` +skie = "0.10.2" # Must be compatible with: `kotlin` buildkonfig = "0.15.2" nsExceptionKt = "1.0.0-BETA-7" datastore = "1.1.1" diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt index 3ef47519..530a431a 100644 --- a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt +++ b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt @@ -7,6 +7,6 @@ import okio.Path.Companion.toPath import org.koin.core.annotation.Factory @Factory -actual class PlatformComponent(val context: Context) { +actual class PlatformComponent(private val context: Context) { actual fun getDatastorePath(): Path = context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() } diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt index 5d35bdc4..2b491106 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt @@ -7,15 +7,11 @@ import app.futured.kmptemplate.persistence.persistence.JsonPersistence import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence import app.futured.kmptemplate.persistence.platform.PlatformComponent import kotlinx.serialization.json.Json -import okio.Path import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module import org.koin.core.annotation.Single -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module @Module(includes = [PersistencePlatformModule::class]) -@ComponentScan("app.futured.kmptemplate.persistence") class PersistenceModule { @Single From 458d732a4eebb80c9066937daf17fadb610649a1 Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Mon, 16 Jun 2025 14:50:27 +0200 Subject: [PATCH 3/9] Remove comment --- shared/persistence/build.gradle.kts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shared/persistence/build.gradle.kts b/shared/persistence/build.gradle.kts index b9d8acbe..632b0f33 100644 --- a/shared/persistence/build.gradle.kts +++ b/shared/persistence/build.gradle.kts @@ -44,10 +44,6 @@ kotlin { implementation(libs.kotlin.test) } } - - /*sourceSets.named("commonMain").configure { - kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") - }*/ } } From 64f1aa8bb62985f865a9d6dff5649337f8784365 Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Mon, 16 Jun 2025 14:54:17 +0200 Subject: [PATCH 4/9] Update koin annotations to 2.1.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 880c05b7..fc73b1c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ androidxActivity = "1.9.2" decompose = "3.1.0" essenty = "2.1.0" koin = "4.1.0" -koinAnnotations = "2.0.1" # Must be compatible with: `ksp` +koinAnnotations = "2.1.0" # Must be compatible with: `ksp` kotlinx-coroutines = "1.8.1" kotlinx-immutableCollections = "0.3.8" kotlinx-dateTime = "0.6.1" From 4d77e105d767cad040306436f8a8140114af0439 Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Tue, 17 Jun 2025 13:32:25 +0200 Subject: [PATCH 5/9] Change persistence platform definition --- .../feature/domain/IsUserLoggedInUseCase.kt | 12 ++++++ .../navigation/root/RootNavHostComponent.kt | 21 +++++++++- .../injection/PersistenceModule.android.kt | 8 ---- .../platform/PersistenceModule.android.kt | 23 +++++++++++ .../platform/PlatformComponent.android.kt | 12 ------ .../injection/PersistenceModule.kt | 41 ++++++++++--------- .../persistence/user/UserPersistence.kt | 25 +++++++++++ .../platform/PersistencePlatformModule.kt | 14 +++++++ .../persistence/platform/PlatformComponent.kt | 9 ---- .../injection/PersistenceModule.ios.kt | 8 ---- ...ponent.ios.kt => PersistenceModule.ios.kt} | 15 +++++-- 11 files changed, 126 insertions(+), 62 deletions(-) create mode 100644 shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt delete mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt create mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt delete mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt create mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt create mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt delete mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt delete mode 100644 shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt rename shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/{PlatformComponent.ios.kt => PersistenceModule.ios.kt} (72%) diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt new file mode 100644 index 00000000..5daaf7c4 --- /dev/null +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt @@ -0,0 +1,12 @@ +package app.futured.kmptemplate.feature.domain + +import app.futured.arkitekt.crusecases.UseCase +import app.futured.kmptemplate.persistence.persistence.user.UserPersistence +import org.koin.core.annotation.Factory + +@Factory +class IsUserLoggedInUseCase( + private val userPersistence: UserPersistence, +) : UseCase() { + override suspend fun build(args: Unit): Boolean = userPersistence.isUserLoggedIn() +} diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt index 8eb0143d..0f443491 100644 --- a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt @@ -1,6 +1,7 @@ package app.futured.kmptemplate.feature.navigation.root import app.futured.arkitekt.decompose.ext.asStateFlow +import app.futured.kmptemplate.feature.domain.IsUserLoggedInUseCase import app.futured.kmptemplate.feature.navigation.deepLink.DeepLinkDestination import app.futured.kmptemplate.feature.navigation.deepLink.DeepLinkResolver import app.futured.kmptemplate.feature.navigation.signedIn.SignedInNavHostComponentFactory @@ -18,7 +19,11 @@ import org.koin.core.annotation.Factory import org.koin.core.annotation.InjectedParam @Factory -internal class RootNavHostComponent(@InjectedParam componentContext: AppComponentContext, private val deepLinkResolver: DeepLinkResolver) : +internal class RootNavHostComponent( + @InjectedParam componentContext: AppComponentContext, + private val deepLinkResolver: DeepLinkResolver, + private val isUserLoggedInUseCase: IsUserLoggedInUseCase, +) : AppComponent(componentContext, RootNavHostViewState), RootNavHost { @@ -55,7 +60,7 @@ internal class RootNavHostComponent(@InjectedParam componentContext: AppComponen init { doOnCreate { if (!consumeDeepLink()) { - rootNavigator.slotNavigator.activate(RootConfig.Login) + checkUserLoggedIn() } } } @@ -84,4 +89,16 @@ internal class RootNavHostComponent(@InjectedParam componentContext: AppComponen rootNavigator.slotNavigator.activate(deepLinkConfig) return true } + + private fun checkUserLoggedIn() { + isUserLoggedInUseCase.execute { + onSuccess { isLoggedIn -> + if (isLoggedIn) { + rootNavigator.slotNavigator.activate(RootConfig.SignedIn()) + } else { + rootNavigator.slotNavigator.activate(RootConfig.Login) + } + } + } + } } diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt deleted file mode 100644 index 86f48a05..00000000 --- a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.android.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.futured.kmptemplate.persistence.injection - -import org.koin.core.annotation.ComponentScan -import org.koin.core.annotation.Module - -@Module -@ComponentScan("app.futured.kmptemplate.persistence.platform") -actual class PersistencePlatformModule actual constructor() diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt new file mode 100644 index 00000000..a53f9e40 --- /dev/null +++ b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt @@ -0,0 +1,23 @@ +package app.futured.kmptemplate.persistence.platform + +import android.content.Context +import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME +import okio.Path +import okio.Path.Companion.toPath +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.scope.Scope + +@Module +actual class PlatformModule { + + @Factory + actual fun providePlatformComponent(scope: Scope): PlatformComponent = PlatformComponentAndroid(scope) +} + +class PlatformComponentAndroid(scope: Scope) : PlatformComponent { + + private val context: Context = scope.get() + + override fun getDatastorePath(): Path = context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() +} diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt deleted file mode 100644 index 530a431a..00000000 --- a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.android.kt +++ /dev/null @@ -1,12 +0,0 @@ -package app.futured.kmptemplate.persistence.platform - -import android.content.Context -import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME -import okio.Path -import okio.Path.Companion.toPath -import org.koin.core.annotation.Factory - -@Factory -actual class PlatformComponent(private val context: Context) { - actual fun getDatastorePath(): Path = context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() -} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt index 2b491106..0274f623 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt @@ -5,6 +5,8 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import app.futured.kmptemplate.persistence.persistence.JsonPersistence import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence +import app.futured.kmptemplate.persistence.persistence.user.UserPersistence +import app.futured.kmptemplate.persistence.persistence.user.UserPersistenceImpl import app.futured.kmptemplate.persistence.platform.PlatformComponent import kotlinx.serialization.json.Json import org.koin.core.annotation.ComponentScan @@ -15,37 +17,36 @@ import org.koin.core.annotation.Single class PersistenceModule { @Single - internal fun provideDataStore(platformComponent: PlatformComponent): DataStore { - return PreferenceDataStoreFactory.createWithPath( - produceFile = { platformComponent.getDatastorePath() }, - ) - } + internal fun provideDataStore( + platformComponent: PlatformComponent, + ): DataStore = PreferenceDataStoreFactory.createWithPath( + produceFile = { platformComponent.getDatastorePath() }, + ) @Single - internal fun providePrimitivePersistence(dataStore: DataStore): PrimitivePersistence { - return PrimitivePersistence(dataStore) + @PersistenceJson + internal fun provideJson(): Json = Json { + encodeDefaults = true + isLenient = false + ignoreUnknownKeys = true + prettyPrint = false } + @Single + internal fun providePrimitivePersistence(dataStore: DataStore): PrimitivePersistence = PrimitivePersistence(dataStore) + @Single internal fun provideJsonPersistence( dataStore: DataStore, @PersistenceJson json: Json, - ): JsonPersistence { - return JsonPersistence(dataStore, json) - } + ): JsonPersistence = JsonPersistence(dataStore, json) @Single - @PersistenceJson - internal fun provideJson(): Json { - return Json { - encodeDefaults = true - isLenient = false - ignoreUnknownKeys = true - prettyPrint = false - } - } + internal fun provideUserPersistence( + primitivePersistence: PrimitivePersistence, + ): UserPersistence = UserPersistenceImpl(primitivePersistence) } @Module @ComponentScan("app.futured.kmptemplate.persistence.platform") -expect class PersistencePlatformModule() +class PersistencePlatformModule diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt new file mode 100644 index 00000000..f6ed410b --- /dev/null +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt @@ -0,0 +1,25 @@ +package app.futured.kmptemplate.persistence.persistence.user + +import androidx.datastore.preferences.core.booleanPreferencesKey +import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence + +interface UserPersistence { + suspend fun isUserLoggedIn(): Boolean + + suspend fun setUserLoggedIn(isLoggedIn: Boolean) +} + +internal class UserPersistenceImpl( + private val primitivePersistence: PrimitivePersistence, +): UserPersistence { + + private companion object { + val IS_USER_LOGGED_IN_KEY = booleanPreferencesKey("IS_USER_LOGGED_IN") + } + + override suspend fun isUserLoggedIn(): Boolean = primitivePersistence.get(IS_USER_LOGGED_IN_KEY) ?: false + + override suspend fun setUserLoggedIn(isLoggedIn: Boolean) { + primitivePersistence.save(IS_USER_LOGGED_IN_KEY, isLoggedIn) + } +} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt new file mode 100644 index 00000000..43114471 --- /dev/null +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt @@ -0,0 +1,14 @@ +package app.futured.kmptemplate.persistence.platform + +import okio.Path +import org.koin.core.annotation.Factory +import org.koin.core.scope.Scope + +expect class PlatformModule() { + @Factory + fun providePlatformComponent(scope: Scope): PlatformComponent +} + +interface PlatformComponent { + fun getDatastorePath(): Path +} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt deleted file mode 100644 index 1c0bc91d..00000000 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.futured.kmptemplate.persistence.platform - -import okio.Path -import org.koin.core.annotation.Factory - -@Factory -expect class PlatformComponent { - fun getDatastorePath(): Path -} diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt deleted file mode 100644 index 86f48a05..00000000 --- a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.ios.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.futured.kmptemplate.persistence.injection - -import org.koin.core.annotation.ComponentScan -import org.koin.core.annotation.Module - -@Module -@ComponentScan("app.futured.kmptemplate.persistence.platform") -actual class PersistencePlatformModule actual constructor() diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt similarity index 72% rename from shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt rename to shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt index 7e342d3c..e1094d6b 100644 --- a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PlatformComponent.ios.kt +++ b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt @@ -5,15 +5,24 @@ import kotlinx.cinterop.ExperimentalForeignApi import okio.Path import okio.Path.Companion.toPath import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.scope.Scope import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager import platform.Foundation.NSURL import platform.Foundation.NSUserDomainMask -@Factory -actual class PlatformComponent { +@Module +actual class PlatformModule { + + @Factory + actual fun providePlatformComponent(scope: Scope): PlatformComponent = PlatformComponentIos() +} + +class PlatformComponentIos : PlatformComponent { + @OptIn(ExperimentalForeignApi::class) - actual fun getDatastorePath(): Path { + override fun getDatastorePath(): Path { val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, From ec89606671f61d957d9b12710031c3b7b717621f Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Tue, 17 Jun 2025 14:31:19 +0200 Subject: [PATCH 6/9] Add DataStoreModule --- .../feature/domain/IsUserLoggedInUseCase.kt | 4 +-- .../feature/domain/SetUserLoggedInUseCase.kt | 13 +++++++ .../navigation/root/RootNavHostComponent.kt | 3 +- .../feature/ui/loginScreen/LoginComponent.kt | 10 +++++- .../ui/profileScreen/ProfileComponent.kt | 10 +++++- .../persistence/injection/DataStoreModule.kt | 23 ++++++++++++ .../platform/PersistenceModule.android.kt | 23 ------------ .../injection/PersistenceModule.kt | 26 ++++++-------- .../persistence/user/UserPersistence.kt | 4 +-- .../platform/PersistencePlatformModule.kt | 14 -------- .../persistence/injection/DataStoreModule.kt | 34 ++++++++++++++++++ .../platform/PersistenceModule.ios.kt | 36 ------------------- 12 files changed, 102 insertions(+), 98 deletions(-) create mode 100644 shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt create mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt delete mode 100644 shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt delete mode 100644 shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt create mode 100644 shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt delete mode 100644 shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt index 5daaf7c4..e9ebb848 100644 --- a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt @@ -5,8 +5,6 @@ import app.futured.kmptemplate.persistence.persistence.user.UserPersistence import org.koin.core.annotation.Factory @Factory -class IsUserLoggedInUseCase( - private val userPersistence: UserPersistence, -) : UseCase() { +class IsUserLoggedInUseCase(private val userPersistence: UserPersistence) : UseCase() { override suspend fun build(args: Unit): Boolean = userPersistence.isUserLoggedIn() } diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt new file mode 100644 index 00000000..b7c0b698 --- /dev/null +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt @@ -0,0 +1,13 @@ +package app.futured.kmptemplate.feature.domain + +import app.futured.arkitekt.crusecases.UseCase +import app.futured.kmptemplate.persistence.persistence.user.UserPersistence +import org.koin.core.annotation.Factory + +@Factory +class SetUserLoggedInUseCase(private val userPersistence: UserPersistence) : UseCase() { + + override suspend fun build(args: Args) = userPersistence.setUserLoggedIn(args.isLoggedIn) + + data class Args(val isLoggedIn: Boolean) +} diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt index 0f443491..0e30ee09 100644 --- a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt @@ -23,8 +23,7 @@ internal class RootNavHostComponent( @InjectedParam componentContext: AppComponentContext, private val deepLinkResolver: DeepLinkResolver, private val isUserLoggedInUseCase: IsUserLoggedInUseCase, -) : - AppComponent(componentContext, RootNavHostViewState), +) : AppComponent(componentContext, RootNavHostViewState), RootNavHost { private val rootNavigator: RootNavHostNavigation = RootNavHostNavigator() diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt index 63aafefd..39014678 100644 --- a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt @@ -1,6 +1,7 @@ package app.futured.kmptemplate.feature.ui.loginScreen import app.futured.factorygenerator.annotation.GenerateFactory +import app.futured.kmptemplate.feature.domain.SetUserLoggedInUseCase import app.futured.kmptemplate.feature.ui.base.AppComponentContext import app.futured.kmptemplate.feature.ui.base.ScreenComponent import kotlinx.coroutines.flow.StateFlow @@ -12,6 +13,7 @@ import org.koin.core.annotation.InjectedParam internal class LoginComponent( @InjectedParam componentContext: AppComponentContext, @InjectedParam override val navigation: LoginScreenNavigation, + private val setUserLoggedInUseCase: SetUserLoggedInUseCase, ) : ScreenComponent( componentContext = componentContext, defaultState = LoginViewState, @@ -23,5 +25,11 @@ internal class LoginComponent( override val actions: LoginScreen.Actions = this override val viewState: StateFlow = componentState - override fun onLoginClick() = navigateToSignedIn() + override fun onLoginClick() { + setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) { + onSuccess { + navigateToSignedIn() + } + } + } } diff --git a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt index 68b0066e..33149324 100644 --- a/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt +++ b/shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt @@ -1,6 +1,7 @@ package app.futured.kmptemplate.feature.ui.profileScreen import app.futured.factorygenerator.annotation.GenerateFactory +import app.futured.kmptemplate.feature.domain.SetUserLoggedInUseCase import app.futured.kmptemplate.feature.ui.base.AppComponentContext import app.futured.kmptemplate.feature.ui.base.ScreenComponent import kotlinx.coroutines.flow.StateFlow @@ -12,6 +13,7 @@ import org.koin.core.annotation.InjectedParam internal class ProfileComponent( @InjectedParam componentContext: AppComponentContext, @InjectedParam override val navigation: ProfileScreenNavigation, + private val setUserLoggedInUseCase: SetUserLoggedInUseCase, ) : ScreenComponent( componentContext, ProfileViewState, @@ -22,6 +24,12 @@ internal class ProfileComponent( override val actions: ProfileScreen.Actions = this override val viewState: StateFlow = componentState - override fun onLogout() = navigateToLogin() + override fun onLogout() { + setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) { + onSuccess { + navigateToLogin() + } + } + } override fun onThird() = navigateToThird("hello third from profile") } diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt new file mode 100644 index 00000000..16449ec7 --- /dev/null +++ b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt @@ -0,0 +1,23 @@ +package app.futured.kmptemplate.persistence.injection + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME +import okio.Path.Companion.toPath +import org.koin.core.annotation.Module +import org.koin.core.annotation.Single +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +@Module +actual class DataStoreModule actual constructor() : KoinComponent { + + private val context: Context by inject() + + @Single + actual fun provideDataStore(): DataStore = PreferenceDataStoreFactory.createWithPath( + produceFile = { context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() }, + ) +} diff --git a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt b/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt deleted file mode 100644 index a53f9e40..00000000 --- a/shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.android.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.futured.kmptemplate.persistence.platform - -import android.content.Context -import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME -import okio.Path -import okio.Path.Companion.toPath -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.scope.Scope - -@Module -actual class PlatformModule { - - @Factory - actual fun providePlatformComponent(scope: Scope): PlatformComponent = PlatformComponentAndroid(scope) -} - -class PlatformComponentAndroid(scope: Scope) : PlatformComponent { - - private val context: Context = scope.get() - - override fun getDatastorePath(): Path = context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() -} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt index 0274f623..de12a140 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt @@ -1,28 +1,19 @@ package app.futured.kmptemplate.persistence.injection import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import app.futured.kmptemplate.persistence.persistence.JsonPersistence import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence import app.futured.kmptemplate.persistence.persistence.user.UserPersistence import app.futured.kmptemplate.persistence.persistence.user.UserPersistenceImpl -import app.futured.kmptemplate.persistence.platform.PlatformComponent import kotlinx.serialization.json.Json -import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module +import org.koin.core.annotation.Provided import org.koin.core.annotation.Single -@Module(includes = [PersistencePlatformModule::class]) +@Module(includes = [DataStoreModule::class]) class PersistenceModule { - @Single - internal fun provideDataStore( - platformComponent: PlatformComponent, - ): DataStore = PreferenceDataStoreFactory.createWithPath( - produceFile = { platformComponent.getDatastorePath() }, - ) - @Single @PersistenceJson internal fun provideJson(): Json = Json { @@ -33,11 +24,13 @@ class PersistenceModule { } @Single - internal fun providePrimitivePersistence(dataStore: DataStore): PrimitivePersistence = PrimitivePersistence(dataStore) + internal fun providePrimitivePersistence( + @Provided dataStore: DataStore, + ): PrimitivePersistence = PrimitivePersistence(dataStore) @Single internal fun provideJsonPersistence( - dataStore: DataStore, + @Provided dataStore: DataStore, @PersistenceJson json: Json, ): JsonPersistence = JsonPersistence(dataStore, json) @@ -48,5 +41,8 @@ class PersistenceModule { } @Module -@ComponentScan("app.futured.kmptemplate.persistence.platform") -class PersistencePlatformModule +expect class DataStoreModule() { + + @Single + fun provideDataStore(): DataStore +} diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt index f6ed410b..e9c406a2 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt @@ -9,9 +9,7 @@ interface UserPersistence { suspend fun setUserLoggedIn(isLoggedIn: Boolean) } -internal class UserPersistenceImpl( - private val primitivePersistence: PrimitivePersistence, -): UserPersistence { +internal class UserPersistenceImpl(private val primitivePersistence: PrimitivePersistence) : UserPersistence { private companion object { val IS_USER_LOGGED_IN_KEY = booleanPreferencesKey("IS_USER_LOGGED_IN") diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt deleted file mode 100644 index 43114471..00000000 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistencePlatformModule.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.futured.kmptemplate.persistence.platform - -import okio.Path -import org.koin.core.annotation.Factory -import org.koin.core.scope.Scope - -expect class PlatformModule() { - @Factory - fun providePlatformComponent(scope: Scope): PlatformComponent -} - -interface PlatformComponent { - fun getDatastorePath(): Path -} diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt new file mode 100644 index 00000000..3c9ab0db --- /dev/null +++ b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt @@ -0,0 +1,34 @@ +package app.futured.kmptemplate.persistence.injection + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME +import kotlinx.cinterop.ExperimentalForeignApi +import okio.Path.Companion.toPath +import org.koin.core.annotation.Module +import org.koin.core.annotation.Single +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSURL +import platform.Foundation.NSUserDomainMask + +@Module +actual class DataStoreModule { + + @OptIn(ExperimentalForeignApi::class) + @Single + actual fun provideDataStore(): DataStore = PreferenceDataStoreFactory.createWithPath( + produceFile = { + val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null, + ) + + (requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath() + }, + ) +} diff --git a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt b/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt deleted file mode 100644 index e1094d6b..00000000 --- a/shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/platform/PersistenceModule.ios.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.futured.kmptemplate.persistence.platform - -import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME -import kotlinx.cinterop.ExperimentalForeignApi -import okio.Path -import okio.Path.Companion.toPath -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.scope.Scope -import platform.Foundation.NSDocumentDirectory -import platform.Foundation.NSFileManager -import platform.Foundation.NSURL -import platform.Foundation.NSUserDomainMask - -@Module -actual class PlatformModule { - - @Factory - actual fun providePlatformComponent(scope: Scope): PlatformComponent = PlatformComponentIos() -} - -class PlatformComponentIos : PlatformComponent { - - @OptIn(ExperimentalForeignApi::class) - override fun getDatastorePath(): Path { - val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( - directory = NSDocumentDirectory, - inDomain = NSUserDomainMask, - appropriateForURL = null, - create = false, - error = null, - ) - - return (requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath() - } -} From a9b96d84c64622c12d9b6c848970df1dade46fbe Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Tue, 17 Jun 2025 14:50:26 +0200 Subject: [PATCH 7/9] Use annotations outside of PersistenceModule --- .../injection/PersistenceModule.kt | 23 ++----------------- .../persistence/JsonPersistence.kt | 6 ++++- .../persistence/PrimitivePersistence.kt | 5 +++- .../persistence/user/UserPersistence.kt | 2 ++ 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt index de12a140..129454a1 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt @@ -2,16 +2,13 @@ package app.futured.kmptemplate.persistence.injection import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences -import app.futured.kmptemplate.persistence.persistence.JsonPersistence -import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence -import app.futured.kmptemplate.persistence.persistence.user.UserPersistence -import app.futured.kmptemplate.persistence.persistence.user.UserPersistenceImpl import kotlinx.serialization.json.Json +import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module -import org.koin.core.annotation.Provided import org.koin.core.annotation.Single @Module(includes = [DataStoreModule::class]) +@ComponentScan("app.futured.kmptemplate.persistence") class PersistenceModule { @Single @@ -22,22 +19,6 @@ class PersistenceModule { ignoreUnknownKeys = true prettyPrint = false } - - @Single - internal fun providePrimitivePersistence( - @Provided dataStore: DataStore, - ): PrimitivePersistence = PrimitivePersistence(dataStore) - - @Single - internal fun provideJsonPersistence( - @Provided dataStore: DataStore, - @PersistenceJson json: Json, - ): JsonPersistence = JsonPersistence(dataStore, json) - - @Single - internal fun provideUserPersistence( - primitivePersistence: PrimitivePersistence, - ): UserPersistence = UserPersistenceImpl(primitivePersistence) } @Module diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt index 038b53e1..618cc2dd 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt @@ -3,6 +3,7 @@ package app.futured.kmptemplate.persistence.persistence import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit +import app.futured.kmptemplate.persistence.injection.PersistenceJson import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull @@ -10,12 +11,15 @@ import kotlinx.coroutines.flow.map import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import org.koin.core.annotation.Provided +import org.koin.core.annotation.Single /** * [DataStore]-backed Persistence which allows storage and observing of complex JSON objects. * Uses [kotlinx.serialization] to serialize and deserialize objects into Strings. */ -internal class JsonPersistence(private val dataStore: DataStore, private val json: Json) { +@Single +internal class JsonPersistence(@Provided private val dataStore: DataStore, @PersistenceJson private val json: Json) { private val logger = Logger.withTag("JsonPersistence") diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt index 13190ab4..0644fe1d 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt @@ -6,11 +6,14 @@ import androidx.datastore.preferences.core.edit import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.mapNotNull +import org.koin.core.annotation.Provided +import org.koin.core.annotation.Single /** * [DataStore]-backed Persistence which allows storage and observing of persisted entities. */ -internal class PrimitivePersistence(private val dataStore: DataStore) { +@Single +internal class PrimitivePersistence(@Provided private val dataStore: DataStore) { suspend fun get(key: Preferences.Key): T? = dataStore.data.firstOrNull()?.run { this[key] diff --git a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt index e9c406a2..b6a8fc98 100644 --- a/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt +++ b/shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt @@ -2,6 +2,7 @@ package app.futured.kmptemplate.persistence.persistence.user import androidx.datastore.preferences.core.booleanPreferencesKey import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence +import org.koin.core.annotation.Single interface UserPersistence { suspend fun isUserLoggedIn(): Boolean @@ -9,6 +10,7 @@ interface UserPersistence { suspend fun setUserLoggedIn(isLoggedIn: Boolean) } +@Single internal class UserPersistenceImpl(private val primitivePersistence: PrimitivePersistence) : UserPersistence { private companion object { From 70cea7b86e93e58706c264da608550af8c1c242d Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Tue, 17 Jun 2025 16:11:27 +0200 Subject: [PATCH 8/9] Fix iOS build --- shared/persistence/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/persistence/build.gradle.kts b/shared/persistence/build.gradle.kts index 632b0f33..4f67b84e 100644 --- a/shared/persistence/build.gradle.kts +++ b/shared/persistence/build.gradle.kts @@ -29,6 +29,8 @@ kotlin { sourceSets { commonMain { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + dependencies { implementation(libs.koin.core) api(libs.koin.annotations) From 108b0a1648719b11c2758f06677e735866fa1e68 Mon Sep 17 00:00:00 2001 From: "jan.mikulik" Date: Thu, 26 Jun 2025 16:07:16 +0200 Subject: [PATCH 9/9] Use implementation instead of api --- gradle/libs.versions.toml | 2 +- shared/persistence/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fc73b1c6..b33f3436 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.10.1" +agp = "8.11.0" kotlin = "2.1.20" ksp = "2.1.20-1.0.32" # Must be compatible with: `kotlin` desugarLibs = "2.1.5" diff --git a/shared/persistence/build.gradle.kts b/shared/persistence/build.gradle.kts index 4f67b84e..7edbbef5 100644 --- a/shared/persistence/build.gradle.kts +++ b/shared/persistence/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { dependencies { implementation(libs.koin.core) - api(libs.koin.annotations) + implementation(libs.koin.annotations) implementation(libs.kotlinx.coroutines.core) implementation(libs.logging.kermit) implementation(libs.kotlinx.serialization.json)