diff --git a/IMPLEMENTATION_STATUS.md b/IMPLEMENTATION_STATUS.md index 170ae36..407d073 100644 --- a/IMPLEMENTATION_STATUS.md +++ b/IMPLEMENTATION_STATUS.md @@ -81,7 +81,7 @@ - Project deletion is handled safely (nullifies habit references before deletion) - All filtering is optional (null projectId shows all habits) - Color parsing is cross-platform compatible (no android.graphics.Color dependency) -- Follows existing codebase patterns: BaseViewModel, sealed events/actions, DI via Kodein +- Follows existing codebase patterns: BaseViewModel, sealed events/actions, DI via Koin ## Files Created (16) - core/database/migrations/Migration7to8.kt diff --git a/README.md b/README.md index 2efc57a..f03763f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Adopted to full Compose Multiplatform and Kotlin Multiplatform - Presentation: KViewModel - Database: Room - Resources: LibRes -- DI: Kodein +- DI: Koin - UI: Compose Multiplatform ### Supported Platforms diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8574574..7a9c21a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -83,7 +83,9 @@ kotlin { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) - implementation(libs.kodein.di) + implementation(libs.koin.core) + implementation(libs.koin.compose) + implementation(libs.koin.compose.viewmodel) implementation(libs.uuid) @@ -103,6 +105,7 @@ kotlin { androidMain.dependencies { implementation(libs.androidx.appcompat) implementation(libs.androidx.activity.compose) + implementation(libs.koin.android) } jvmMain.dependencies { diff --git a/composeApp/proguard-rules.pro b/composeApp/proguard-rules.pro index 87b18b5..99ed579 100644 --- a/composeApp/proguard-rules.pro +++ b/composeApp/proguard-rules.pro @@ -22,9 +22,12 @@ -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} -# Keep Kodein --keep class org.kodein.** { *; } --keep @org.kodein.di.DI$Tag class * +# Keep Koin +-keep class org.koin.** { *; } +-keep class org.koin.core.** { *; } +-keepclassmembers class * { + public (...); +} # General Android rules -keepclassmembers class * implements android.os.Parcelable { diff --git a/composeApp/src/androidMain/kotlin/di/Providers.kt b/composeApp/src/androidMain/kotlin/di/Providers.kt index 1a95738..491abba 100644 --- a/composeApp/src/androidMain/kotlin/di/Providers.kt +++ b/composeApp/src/androidMain/kotlin/di/Providers.kt @@ -1,13 +1,11 @@ package di import core.platform.ImagePicker -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.singleton +import org.koin.core.module.Module +import org.koin.dsl.module -actual fun DI.Builder.provideImagePicker() { - bind() with singleton { - instance().imagePicker +actual fun Module.provideImagePicker() { + single { + get().imagePicker } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/CoreModule.kt b/composeApp/src/commonMain/kotlin/di/CoreModule.kt index 09364d7..ebd6a47 100644 --- a/composeApp/src/commonMain/kotlin/di/CoreModule.kt +++ b/composeApp/src/commonMain/kotlin/di/CoreModule.kt @@ -1,9 +1,9 @@ package di -import org.kodein.di.DI +import org.koin.dsl.module -val coreModule = DI.Module("coreModule") { - importAll( +val coreModule = module { + includes( serializationModule ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/DatabaseModule.kt b/composeApp/src/commonMain/kotlin/di/DatabaseModule.kt index 8be5211..df372c0 100644 --- a/composeApp/src/commonMain/kotlin/di/DatabaseModule.kt +++ b/composeApp/src/commonMain/kotlin/di/DatabaseModule.kt @@ -6,33 +6,30 @@ import feature.daily.data.DailyDao import feature.habits.data.HabitDao import feature.projects.data.ProjectDao import feature.tracker.data.TrackerDao -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.singleton +import org.koin.dsl.module -fun databaseModule() = DI.Module("database") { - bind() with singleton { - instance("appDatabase") as AppDatabase +val databaseModule = module { + single { + get(qualifier = org.koin.core.qualifier.named("appDatabase")) } - bind() with singleton { - instance().getHabitDao() + single { + get().getHabitDao() } - bind() with singleton { - instance().getTrackerDao() + single { + get().getTrackerDao() } - bind() with singleton { - instance().getDailyDao() + single { + get().getDailyDao() } - bind() with singleton { - instance().getUserProfileDao() + single { + get().getUserProfileDao() } - bind() with singleton { - instance().getProjectDao() + single { + get().getProjectDao() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/FeatureModule.kt b/composeApp/src/commonMain/kotlin/di/FeatureModule.kt index c9fcc83..9bef373 100644 --- a/composeApp/src/commonMain/kotlin/di/FeatureModule.kt +++ b/composeApp/src/commonMain/kotlin/di/FeatureModule.kt @@ -7,45 +7,42 @@ import feature.habits.domain.CreateHabitUseCase import feature.projects.di.projectModule import feature.settings.domain.ClearAllHabitsUseCase import feature.tracker.domain.UpdateTrackerValueUseCase -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.singleton +import org.koin.dsl.module -fun featureModule() = DI.Module("feature") { - importAll( +val featureModule = module { + includes( detailModule, projectModule ) - + // Use Cases - bind() with singleton { + single { GetHabitsForTodayUseCase( - habitDao = instance(), - trackerDao = instance(), - dailyDao = instance() + habitDao = get(), + trackerDao = get(), + dailyDao = get() ) } - bind() with singleton { + single { SwitchHabitUseCase( - habitDao = instance(), - dailyDao = instance() + habitDao = get(), + dailyDao = get() ) } - bind() with singleton { + single { CreateHabitUseCase( - habitDao = instance() + habitDao = get() ) } - bind() with singleton { + single { UpdateTrackerValueUseCase( - trackerDao = instance() + trackerDao = get() ) } - bind() with singleton { + single { ClearAllHabitsUseCase( - habitDao = instance(), - dailyDao = instance() + habitDao = get(), + dailyDao = get() ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/PlatformSDK.kt b/composeApp/src/commonMain/kotlin/di/PlatformSDK.kt index 75e4f4b..1684183 100644 --- a/composeApp/src/commonMain/kotlin/di/PlatformSDK.kt +++ b/composeApp/src/commonMain/kotlin/di/PlatformSDK.kt @@ -1,43 +1,48 @@ package di -import org.kodein.di.DI -import org.kodein.di.DirectDI -import org.kodein.di.bind -import org.kodein.di.direct -import org.kodein.di.instance -import org.kodein.di.singleton +import org.koin.core.Koin +import org.koin.core.context.GlobalContext +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.core.qualifier.named +import org.koin.dsl.module object PlatformSDK { - private var _di: DirectDI? = null - val di: DirectDI - get() = requireNotNull(_di) + private var _koin: Koin? = null + val koin: Koin + get() = requireNotNull(_koin) fun init( configuration: PlatformConfiguration, appDatabase: Any? = null ) { - val configModule = DI.Module("config") { - bind() with singleton { configuration } + // Stop any existing Koin instance to allow reinitialization + if (GlobalContext.getOrNull() != null) { + stopKoin() + } + + val configModule = module { + single { configuration } if (appDatabase != null) { - bind("appDatabase") with singleton { appDatabase } + single(qualifier = named("appDatabase")) { appDatabase } } } - val platformModule = DI.Module("platform") { + val platformModule = module { provideImagePicker() } - _di = DI { - importAll( + _koin = startKoin { + modules( configModule, platformModule, - databaseModule(), - featureModule() + databaseModule, + featureModule ) - }.direct + }.koin } inline fun instance(): T { - return di.instance() + return koin.get() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/Providers.kt b/composeApp/src/commonMain/kotlin/di/Providers.kt index d7a88b8..eff564e 100644 --- a/composeApp/src/commonMain/kotlin/di/Providers.kt +++ b/composeApp/src/commonMain/kotlin/di/Providers.kt @@ -1,5 +1,5 @@ package di -import org.kodein.di.DI +import org.koin.core.module.Module -expect fun DI.Builder.provideImagePicker() \ No newline at end of file +expect fun Module.provideImagePicker() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/SerializationModule.kt b/composeApp/src/commonMain/kotlin/di/SerializationModule.kt index 2643424..e54469c 100644 --- a/composeApp/src/commonMain/kotlin/di/SerializationModule.kt +++ b/composeApp/src/commonMain/kotlin/di/SerializationModule.kt @@ -1,12 +1,10 @@ package di import kotlinx.serialization.json.Json -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.singleton +import org.koin.dsl.module -val serializationModule = DI.Module("serializationModule") { - bind() with singleton { +val serializationModule = module { + single { Json { isLenient = true ignoreUnknownKeys = true diff --git a/composeApp/src/commonMain/kotlin/feature/daily/di/DailyModule.kt b/composeApp/src/commonMain/kotlin/feature/daily/di/DailyModule.kt index f01b8e4..4834eb9 100644 --- a/composeApp/src/commonMain/kotlin/feature/daily/di/DailyModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/daily/di/DailyModule.kt @@ -2,30 +2,26 @@ package feature.daily.di import core.database.AppDatabase import data.features.daily.DailyRepository -import di.Inject.instance import feature.daily.data.DailyDao import feature.daily.domain.GetHabitsForTodayUseCase import feature.daily.domain.SwitchHabitUseCase -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.provider -import org.kodein.di.singleton +import org.koin.dsl.module -val dailyModule = DI.Module("DailyModule") { - bind() with singleton { - val appDatabase = instance() +val dailyModule = module { + single { + val appDatabase = get() appDatabase.getDailyDao() } - - bind() with provider { - GetHabitsForTodayUseCase(instance(), instance(), instance()) + + factory { + GetHabitsForTodayUseCase(get(), get(), get()) } - - bind() with provider { - SwitchHabitUseCase(instance(), instance()) + + factory { + SwitchHabitUseCase(get(), get()) } - bind() with provider { + factory { DailyRepository() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/feature/detail/di/DetailModule.kt b/composeApp/src/commonMain/kotlin/feature/detail/di/DetailModule.kt index 5776220..852a68a 100644 --- a/composeApp/src/commonMain/kotlin/feature/detail/di/DetailModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/detail/di/DetailModule.kt @@ -3,18 +3,18 @@ package feature.detail.di import feature.detail.domain.DeleteHabitUseCase import feature.detail.domain.GetDetailInfoUseCase import feature.detail.domain.UpdateHabitUseCase -import org.kodein.di.* +import org.koin.dsl.module -val detailModule = DI.Module("detailModule") { - bind() with provider { - GetDetailInfoUseCase(instance()) +val detailModule = module { + factory { + GetDetailInfoUseCase(get()) } - - bind() with provider { - DeleteHabitUseCase(instance()) + + factory { + DeleteHabitUseCase(get()) } - - bind() with provider { - UpdateHabitUseCase(instance()) + + factory { + UpdateHabitUseCase(get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/feature/habits/di/HabitModule.kt b/composeApp/src/commonMain/kotlin/feature/habits/di/HabitModule.kt index 03fae4b..bc3ed9a 100644 --- a/composeApp/src/commonMain/kotlin/feature/habits/di/HabitModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/habits/di/HabitModule.kt @@ -3,15 +3,15 @@ package feature.habits.di import core.database.AppDatabase import feature.habits.data.HabitDao import feature.habits.domain.CreateHabitUseCase -import org.kodein.di.* +import org.koin.dsl.module -val habitModule = DI.Module("HabitModule") { - bind() with singleton { - val appDatabase = instance() +val habitModule = module { + single { + val appDatabase = get() appDatabase.getHabitDao() } - - bind() with provider { - CreateHabitUseCase(instance()) + + factory { + CreateHabitUseCase(get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/feature/projects/di/ProjectModule.kt b/composeApp/src/commonMain/kotlin/feature/projects/di/ProjectModule.kt index 898ac43..161a2a5 100644 --- a/composeApp/src/commonMain/kotlin/feature/projects/di/ProjectModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/projects/di/ProjectModule.kt @@ -4,14 +4,11 @@ import feature.projects.domain.CreateProjectUseCase import feature.projects.domain.DeleteProjectUseCase import feature.projects.domain.GetAllProjectsUseCase import feature.projects.domain.UpdateProjectUseCase -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.provider +import org.koin.dsl.module -val projectModule = DI.Module("projectModule") { - bind() with provider { CreateProjectUseCase(instance()) } - bind() with provider { GetAllProjectsUseCase(instance()) } - bind() with provider { UpdateProjectUseCase(instance()) } - bind() with provider { DeleteProjectUseCase(instance(), instance()) } +val projectModule = module { + factory { CreateProjectUseCase(get()) } + factory { GetAllProjectsUseCase(get()) } + factory { UpdateProjectUseCase(get()) } + factory { DeleteProjectUseCase(get(), get()) } } diff --git a/composeApp/src/commonMain/kotlin/feature/settings/di/SettingsModule.kt b/composeApp/src/commonMain/kotlin/feature/settings/di/SettingsModule.kt index 8d5288a..f1500f1 100644 --- a/composeApp/src/commonMain/kotlin/feature/settings/di/SettingsModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/settings/di/SettingsModule.kt @@ -1,13 +1,10 @@ package feature.settings.di -import di.Inject.instance import feature.settings.domain.ClearAllHabitsUseCase -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.provider +import org.koin.dsl.module -val settingsModule = DI.Module("SettingsModule") { - bind() with provider { - ClearAllHabitsUseCase(instance(), instance()) +val settingsModule = module { + factory { + ClearAllHabitsUseCase(get(), get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/feature/tracker/di/TrackerModule.kt b/composeApp/src/commonMain/kotlin/feature/tracker/di/TrackerModule.kt index 6ad1d31..972a295 100644 --- a/composeApp/src/commonMain/kotlin/feature/tracker/di/TrackerModule.kt +++ b/composeApp/src/commonMain/kotlin/feature/tracker/di/TrackerModule.kt @@ -3,15 +3,15 @@ package feature.tracker.di import core.database.AppDatabase import feature.tracker.data.TrackerDao import feature.tracker.domain.UpdateTrackerValueUseCase -import org.kodein.di.* +import org.koin.dsl.module -val trackerModule = DI.Module("TrackerModule") { - bind() with singleton { - val appDatabase = instance() +val trackerModule = module { + single { + val appDatabase = get() appDatabase.getTrackerDao() } - - bind() with provider { - UpdateTrackerValueUseCase(instance()) + + factory { + UpdateTrackerValueUseCase(get()) } } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/di/Providers.ios.kt b/composeApp/src/iosMain/kotlin/di/Providers.ios.kt index b5c788f..88635c3 100644 --- a/composeApp/src/iosMain/kotlin/di/Providers.ios.kt +++ b/composeApp/src/iosMain/kotlin/di/Providers.ios.kt @@ -2,10 +2,8 @@ package di import core.platform.IOSImagePicker import core.platform.ImagePicker -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.singleton +import org.koin.core.module.Module -actual fun DI.Builder.provideImagePicker(platform: Platform) { - bind() with singleton { IOSImagePicker() } +actual fun Module.provideImagePicker() { + single { IOSImagePicker() } } \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/di/Providers.desktop.kt b/composeApp/src/jvmMain/kotlin/di/Providers.desktop.kt index 08770d6..091ede1 100644 --- a/composeApp/src/jvmMain/kotlin/di/Providers.desktop.kt +++ b/composeApp/src/jvmMain/kotlin/di/Providers.desktop.kt @@ -2,10 +2,8 @@ package di import core.platform.DesktopImagePicker import core.platform.ImagePicker -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.singleton +import org.koin.core.module.Module -actual fun DI.Builder.provideImagePicker(platform: Platform) { - bind() with singleton { DesktopImagePicker() } +actual fun Module.provideImagePicker() { + single { DesktopImagePicker() } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d836976..68e1886 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ coroutines = "1.8.1" serialization = "1.6.3" ktor = "2.3.9" klock = "3.4.0" +koin = "4.0.1" # Libraries room = "2.7.0-alpha03" @@ -56,7 +57,10 @@ coil-multiplatform-compose = { module = "io.coil-kt.coil3:coil-compose", version #coil-multiplatform-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil-multiplatform" } coil-multiplatform-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil-multiplatform" } -kodein-di = "org.kodein.di:kodein-di:7.20.2" +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } +koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } uuid = "app.softwork:kotlinx-uuid-core:0.0.25" compose-viewmodel = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0"