diff --git a/AGENTS.md b/AGENTS.md index 40343f11..6d3ee9ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,13 +26,13 @@ - `com.streamplayer.kmp-library` — base KMP module setup (multiplatform, serialization, parcelize, dokka, detekt) - `com.streamplayer.dokka` — documentation generation - `com.streamplayer.detekt` — static analysis -- `com.streamplayer.koin-annotations-setup` — Koin annotation processing +- `com.streamplayer.koin-annotations-setup` — Koin Annotations artifacts (the Koin Compiler Plugin is applied by `com.streamplayer.kmp-library`) - `popcorngp-setup-plugin` — Popcorn Guinea Pig test setup ### Key Dependencies - Compose Multiplatform 1.10.0 -- Koin 4.2.0 / Koin Annotations 2.3.1 +- Koin 4.2.2 / Koin Compiler Plugin 1.2.3 (Koin Annotations, no KSP) - Ktor 3.0.1 / OkHttp 4.12.0 - Room 2.7.0-alpha13 - Coil 3.1.0 diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 2a5aea06..c55180a7 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") alias(libs.plugins.jetbrains.compose) alias(libs.plugins.compose.compiler) + alias(libs.plugins.koin.compiler) alias(libs.plugins.google.services) alias(libs.plugins.firebase.crashlytics) } diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 6c4c4334..9bd20787 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -21,5 +21,6 @@ dependencies { implementation(libs.detekt.gradle.plugin) implementation(libs.serialization) implementation(libs.com.google.devtools.ksp.gradle.plugin) + implementation(libs.koin.compiler.gradle.plugin) implementation(libs.popcorn.guineapig) } diff --git a/build-logic/src/main/java/com.streamplayer.kmp-library.gradle.kts b/build-logic/src/main/java/com.streamplayer.kmp-library.gradle.kts index ea8f4626..81064b25 100644 --- a/build-logic/src/main/java/com.streamplayer.kmp-library.gradle.kts +++ b/build-logic/src/main/java/com.streamplayer.kmp-library.gradle.kts @@ -3,6 +3,7 @@ import extensions.iosTarget import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.kotlin.dsl.the +import org.koin.compiler.plugin.KoinGradleExtension val libs = the() @@ -13,6 +14,11 @@ plugins { id("kotlin-parcelize") id("com.streamplayer.dokka") id("com.streamplayer.detekt") + id("io.insert-koin.compiler.plugin") +} + +configure { + compileSafety = true } kotlin { diff --git a/build-logic/src/main/java/com.streamplayer.koin-annotations-setup.gradle.kts b/build-logic/src/main/java/com.streamplayer.koin-annotations-setup.gradle.kts index 14d94977..d38d1973 100644 --- a/build-logic/src/main/java/com.streamplayer.koin-annotations-setup.gradle.kts +++ b/build-logic/src/main/java/com.streamplayer.koin-annotations-setup.gradle.kts @@ -1,11 +1,10 @@ import org.gradle.accessors.dm.LibrariesForLibs -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.gradle.kotlin.dsl.the val libs = the() plugins { id("org.jetbrains.kotlin.multiplatform") - id("com.google.devtools.ksp") } kotlin { @@ -15,35 +14,4 @@ kotlin { api(libs.koin.annotations) } } - - // KSP Common sourceSet - sourceSets.named("commonMain").configure { - kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") - } -} - -// KSP Tasks -dependencies { - add("kspCommonMainMetadata", libs.koin.ksp.compiler) -} - - -// WORKAROUND: ADD this dependsOn("kspCommonMainKotlinMetadata") instead of above dependencies -tasks.withType().configureEach { - if (name != "kspCommonMainKotlinMetadata") { - dependsOn("kspCommonMainKotlinMetadata") - } -} - -afterEvaluate { - tasks.filter { - it.name.contains("SourcesJar", true) - }.forEach { - println("SourceJarTask====>${it.name}") - it.dependsOn("kspCommonMainKotlinMetadata") - } -} - -ksp { - arg("KOIN_CONFIG_CHECK", "true") } diff --git a/build.gradle.kts b/build.gradle.kts index 962a7f6c..57a2c549 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.jetbrains.compose) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.ksp) apply false + alias(libs.plugins.koin.compiler) apply false alias(libs.plugins.dokka) apply false alias(libs.plugins.buildkonfig.plugin) apply false id("popcorngp-setup-plugin") diff --git a/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/AppModule.kt b/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/AppModule.kt index c19d3c95..f3d34abb 100644 --- a/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/AppModule.kt +++ b/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/AppModule.kt @@ -2,43 +2,36 @@ package com.codandotv.streamplayerapp.composeApp.presentation.di import com.codandotv.streamplayerapp.core.background.work.di.SyncModule import com.codandotv.streamplayerapp.core.local.storage.di.LocalStorageModule -import com.codandotv.streamplayerapp.core.networking.di.NetworkModule import com.codandotv.streamplayerapp.core.session.di.coreSessionModule import com.codandotv.streamplayerapp.core.shared.qualifier.QualifierDispatcherIO import com.codandotv.streamplayerapp.feature.liststreams.list.di.ListStreamModule -import com.codandotv.streamplayerapp.feature.news.di.NewsScreenModule -import com.codandotv.streamplayerapp.feature.profile.di.ProfilePickerStreamModule -import com.codandotv.streamplayerapp.feature.search.di.SearchModule import io.kotzilla.generated.monitoring import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import org.koin.core.KoinApplication -import org.koin.core.context.startKoin -import org.koin.core.lazyModules import org.koin.dsl.module -import org.koin.ksp.generated.module +import org.koin.plugin.module.dsl.startKoin fun streamPlayerApplication(platformBlock: KoinApplication.() -> Unit): KoinApplication { - return startKoin { + return startKoin { platformBlock() - lazyModules(PermissionsModule.module) + // Annotated modules (NetworkModule, SearchModule, NewsScreenModule, + // ProfilePickerStreamModule) are discovered from @Configuration by the + // Koin Compiler Plugin — only the DSL modules are listed here. modules( + PermissionsModule.module, module { single(QualifierDispatcherIO) { Dispatchers.IO } }, coreSessionModule, - NetworkModule().module, LocalStorageModule.module, SyncModule.module, // region feature_modules - ListStreamModule.module, - SearchModule().module, - NewsScreenModule().module, - ProfilePickerStreamModule().module + ListStreamModule.module // endregion ) diff --git a/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/KoinApp.kt b/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/KoinApp.kt new file mode 100644 index 00000000..544ef0ef --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/codandotv/streamplayerapp/composeApp/presentation/di/KoinApp.kt @@ -0,0 +1,12 @@ +package com.codandotv.streamplayerapp.composeApp.presentation.di + +import org.koin.core.annotation.KoinApplication + +/** + * Koin application entry point, scanned by the Koin Compiler Plugin. + * + * Every `@Module` tagged with `@Configuration` on the compile classpath of this + * module is discovered and loaded by `startKoin()`. + */ +@KoinApplication +object KoinApp diff --git a/core-background-work/build.gradle.kts b/core-background-work/build.gradle.kts index cc6b2cff..7c7d3010 100644 --- a/core-background-work/build.gradle.kts +++ b/core-background-work/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("com.streamplayer.kmp-library") - id("com.google.devtools.ksp") } kotlin { diff --git a/core-networking/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/networking/di/NetworkModule.kt b/core-networking/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/networking/di/NetworkModule.kt index 25d73f29..3fa4b850 100644 --- a/core-networking/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/networking/di/NetworkModule.kt +++ b/core-networking/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/networking/di/NetworkModule.kt @@ -3,21 +3,25 @@ package com.codandotv.streamplayerapp.core.networking.di import com.codandotv.streamplayerapp.core.networking.HttpClientBuilder import core.networking.BuildKonfig import io.ktor.client.HttpClient +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Configuration import org.koin.core.annotation.Module import org.koin.core.annotation.Qualifier -import org.koin.core.annotation.Single +import org.koin.core.annotation.Singleton @Module +@Configuration +@ComponentScan("com.codandotv.streamplayerapp.core.networking.di") class NetworkModule { - @Single + @Singleton fun provideHttpClient(): HttpClient { return HttpClientBuilder.build( baseUrl = BuildKonfig.HOST, ) } - @Single + @Singleton @Qualifier(QualifierProfileHttpClient::class) fun provideProfileHttpClient(): HttpClient { return HttpClientBuilder.build( diff --git a/core-permission/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/permission/di/PermissionsModule.kt b/core-permission/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/permission/di/PermissionsModule.kt index 713ed84f..633fc507 100644 --- a/core-permission/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/permission/di/PermissionsModule.kt +++ b/core-permission/src/commonMain/kotlin/com/codandotv/streamplayerapp/core/permission/di/PermissionsModule.kt @@ -2,10 +2,10 @@ import com.codandotv.streamplayerapp.core.permission.PermissionFactory import com.codandotv.streamplayerapp.core.permission.PermissionsManager import com.codandotv.streamplayerapp.core.permission.PermissionsManagerImpl -import org.koin.dsl.lazyModule +import org.koin.dsl.module object PermissionsModule { - val module = lazyModule { + val module = module { single { PermissionsManagerImpl( controller = PermissionFactory() diff --git a/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/di/NewsScreenModule.kt b/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/di/NewsScreenModule.kt index 8b665f6c..1395a26f 100644 --- a/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/di/NewsScreenModule.kt +++ b/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/di/NewsScreenModule.kt @@ -1,8 +1,10 @@ package com.codandotv.streamplayerapp.feature.news.di import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Configuration import org.koin.core.annotation.Module @Module +@Configuration @ComponentScan("com.codandotv.streamplayerapp.feature.news") class NewsScreenModule diff --git a/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/presentation/NewsScreenViewModel.kt b/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/presentation/NewsScreenViewModel.kt index b33df42e..4acf847d 100644 --- a/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/presentation/NewsScreenViewModel.kt +++ b/feature-news/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/news/presentation/NewsScreenViewModel.kt @@ -13,8 +13,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.koin.android.annotation.KoinViewModel -import org.koin.core.annotation.Provided +import org.koin.core.annotation.KoinViewModel data class NewsScreenUiState( val showPermissionDialog: Boolean = false, @@ -27,7 +26,7 @@ sealed class NewsScreenActionTakeImage { @KoinViewModel class NewsScreenViewModel( - @Provided val permissionsManager: PermissionsManager + val permissionsManager: PermissionsManager ) : ViewModel() { private val _actionTakeImage = MutableSharedFlow() val actionTakeImage: SharedFlow = _actionTakeImage diff --git a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/data/ProfilePickerStreamService.kt b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/data/ProfilePickerStreamService.kt index 9e94da44..531e9ab2 100644 --- a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/data/ProfilePickerStreamService.kt +++ b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/data/ProfilePickerStreamService.kt @@ -6,7 +6,6 @@ import com.codandotv.streamplayerapp.feature.profile.data.model.ProfilesResponse import io.ktor.client.HttpClient import io.ktor.client.request.url import org.koin.core.annotation.Factory -import org.koin.core.annotation.Provided interface ProfilePickerStreamService { suspend fun getProfiles(): NetworkResponse @@ -14,7 +13,7 @@ interface ProfilePickerStreamService { @Factory class ProfilePickerStreamServiceImpl( - @Provided private val client: HttpClient + private val client: HttpClient ) : ProfilePickerStreamService { override suspend fun getProfiles(): NetworkResponse = client.safeRequest { diff --git a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/di/ProfilePickerStreamModule.kt b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/di/ProfilePickerStreamModule.kt index 71edf025..a37e6557 100644 --- a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/di/ProfilePickerStreamModule.kt +++ b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/di/ProfilePickerStreamModule.kt @@ -1,8 +1,10 @@ package com.codandotv.streamplayerapp.feature.profile.di import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Configuration import org.koin.core.annotation.Module @Module +@Configuration @ComponentScan("com.codandotv.streamplayerapp.feature.profile") class ProfilePickerStreamModule diff --git a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/presentation/screens/ProfilePickerStreamViewModel.kt b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/presentation/screens/ProfilePickerStreamViewModel.kt index 43024dca..b95e70f3 100644 --- a/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/presentation/screens/ProfilePickerStreamViewModel.kt +++ b/feature-profile/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/profile/presentation/screens/ProfilePickerStreamViewModel.kt @@ -12,12 +12,11 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.koin.android.annotation.KoinViewModel -import org.koin.core.annotation.Provided +import org.koin.core.annotation.KoinViewModel @KoinViewModel class ProfilePickerStreamViewModel( - @Provided private val sessionManager: SessionManager, + private val sessionManager: SessionManager, private val useCase: ProfilePickerStreamUseCase, ) : ViewModel() { diff --git a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/MostPopularMoviesService.kt b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/MostPopularMoviesService.kt index 74c6dabb..be63b208 100644 --- a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/MostPopularMoviesService.kt +++ b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/MostPopularMoviesService.kt @@ -6,7 +6,6 @@ import com.codandotv.streamplayerapp.feature.search.data.model.ListSearchStreamR import io.ktor.client.HttpClient import io.ktor.client.request.url import org.koin.core.annotation.Factory -import org.koin.core.annotation.Provided interface MostPopularMoviesService { suspend fun getPopular(): NetworkResponse @@ -14,7 +13,7 @@ interface MostPopularMoviesService { @Factory(binds = [MostPopularMoviesService::class]) class MostPopularMoviesServiceImpl( - @Provided private val client: HttpClient + private val client: HttpClient ) : MostPopularMoviesService { override suspend fun getPopular(): NetworkResponse = client.safeRequest { diff --git a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/SearchStreamService.kt b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/SearchStreamService.kt index daf06b8f..73d3c227 100644 --- a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/SearchStreamService.kt +++ b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/data/api/SearchStreamService.kt @@ -7,7 +7,6 @@ import io.ktor.client.HttpClient import io.ktor.client.request.parameter import io.ktor.client.request.url import org.koin.core.annotation.Factory -import org.koin.core.annotation.Provided interface SearchStreamService { suspend fun getSearch(query: String): NetworkResponse @@ -15,7 +14,7 @@ interface SearchStreamService { @Factory(binds = [SearchStreamService::class]) class SearchStreamServiceImpl( - @Provided private val client: HttpClient + private val client: HttpClient ) : SearchStreamService { override suspend fun getSearch(query: String): NetworkResponse = client.safeRequest { diff --git a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/di/SearchModule.kt b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/di/SearchModule.kt index 1172f614..13150fa3 100644 --- a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/di/SearchModule.kt +++ b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/di/SearchModule.kt @@ -1,8 +1,10 @@ package com.codandotv.streamplayerapp.feature.search.di import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Configuration import org.koin.core.annotation.Module @Module +@Configuration @ComponentScan("com.codandotv.streamplayerapp.feature.search") class SearchModule diff --git a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/presentation/screens/SearchViewModel.kt b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/presentation/screens/SearchViewModel.kt index ddb4d993..2ecc4ab0 100644 --- a/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/presentation/screens/SearchViewModel.kt +++ b/feature-search/src/commonMain/kotlin/com/codandotv/streamplayerapp/feature/search/presentation/screens/SearchViewModel.kt @@ -14,14 +14,13 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.koin.android.annotation.KoinViewModel -import org.koin.core.annotation.Provided +import org.koin.core.annotation.KoinViewModel @KoinViewModel class SearchViewModel( private val searchUseCase: SearchUseCase, private val mostPopularMoviesUseCase: MostPopularMoviesUseCase, - @Provided private val sessionManager: SessionManager + private val sessionManager: SessionManager ) : ViewModel() { private var tryAgain: () -> Unit = {} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e7ccf17..a6e12d62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] kotlin = "2.3.20" android_gradle_plugin = "9.1.0" -koin = "4.2.0" -koin-annotations = "2.3.1" +koin = "4.2.2" +koin-compiler = "1.2.3" ksp = "2.3.5" kotzilla = "2.1.3" lifecycle-viewmodel-compose-version = "2.9.6" @@ -87,15 +87,16 @@ androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = " androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" } -# Koin -koin_test = { group = "io.insert-koin", name = "koin-test-junit4", version.ref = "koin" } -koin_android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } -koin_core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" } -koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" } -koin-ksp-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-annotations" } +# Koin — all artifacts share the `koin` version key (no BOM: platform(Any) is +# DeprecationLevel.ERROR in the Kotlin DSL of the build-logic precompiled scripts) +koin_test = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } +koin_android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin_core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-annotations = { module = "io.insert-koin:koin-annotations", 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_core_coroutines = { module = "io.insert-koin:koin-core-coroutines", version.ref = "koin" } +koin-compiler-gradle-plugin = { module = "io.insert-koin:koin-compiler-gradle-plugin", version.ref = "koin-compiler" } #Networking okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } @@ -183,6 +184,7 @@ compose_compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = " room = { id = "androidx.room", version.ref = "room" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +koin-compiler = { id = "io.insert-koin.compiler.plugin", version.ref = "koin-compiler" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } buildkonfig_plugin = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfig" }