diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 70afe1b..a67c820 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,6 +63,8 @@ android { packaging { resources { + + excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/okhttp3/internal/publicsuffix/NOTICE" excludes += "/kotlin/**" @@ -88,7 +90,6 @@ dependencies { implementation(projects.core.platform) implementation(projects.composeApp) - implementation(libs.koin.android) implementation(libs.coil.core) implementation(projects.core.common) } diff --git a/app/src/main/kotlin/dev/androidbroadcast/newssearchapp/NewsApplication.kt b/app/src/main/kotlin/dev/androidbroadcast/newssearchapp/NewsApplication.kt index 51b5093..b31a7ae 100644 --- a/app/src/main/kotlin/dev/androidbroadcast/newssearchapp/NewsApplication.kt +++ b/app/src/main/kotlin/dev/androidbroadcast/newssearchapp/NewsApplication.kt @@ -3,7 +3,6 @@ package dev.androidbroadcast.newssearchapp import android.app.Application import coil3.SingletonImageLoader import dev.androidbroadcast.news.core.NewsAppPlatform -import org.koin.android.ext.koin.androidContext class NewsApplication @JvmOverloads constructor( private val platform: NewsAppPlatform = NewsAppPlatform() @@ -15,9 +14,7 @@ class NewsApplication @JvmOverloads constructor( debug = BuildConfig.DEBUG, newsApiKey = BuildConfig.NEWS_API_KEY, newsApiBaseUrl = BuildConfig.NEWS_API_BASE_URL, - targetAppDeclaration = { - androidContext(this@NewsApplication) - } + platformContext = this ) } } diff --git a/compose-app/build.gradle.kts b/compose-app/build.gradle.kts index 351315e..1f8fbe5 100644 --- a/compose-app/build.gradle.kts +++ b/compose-app/build.gradle.kts @@ -24,7 +24,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -39,7 +38,6 @@ kotlin { implementation(projects.features.newsMain.ui) implementation(projects.core.common) implementation(projects.core.uikit) - implementation(libs.koin.compose) } } } diff --git a/compose-app/src/androidMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.android.kt b/compose-app/src/androidMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.android.kt index a7b03e6..9242931 100644 --- a/compose-app/src/androidMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.android.kt +++ b/compose-app/src/androidMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.android.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView -import androidx.core.view.WindowCompat import dev.androidbroadcast.news.NewsTheme @Composable @@ -19,7 +18,7 @@ internal actual fun NewsPlatformTheme( SideEffect { val window = (view.context as Activity).window window.statusBarColor = colorScheme.primary.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme +// WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme } } content() diff --git a/compose-app/src/commonMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.kt b/compose-app/src/commonMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.kt index 959d472..230d0d3 100644 --- a/compose-app/src/commonMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.kt +++ b/compose-app/src/commonMain/kotlin/dev/androidbroadcast/news/compose/NewsApp.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.androidbroadcast.news.NewsTheme import dev.androidbroadcast.news.main.NewsMainScreen -import org.koin.compose.KoinContext /** * Setup theme for platform @@ -29,9 +28,7 @@ public fun NewsApp() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - KoinContext { - NewsMainScreen() - } + NewsMainScreen() } } } diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index ae93c96..97a53ab 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -21,7 +21,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -37,14 +36,13 @@ kotlin { api(libs.kotlinx.immutable) api(libs.kotlinx.datetime) api(libs.coil.core) - api(libs.koin.core) + api(libs.kotlinInject.runtime) implementation(libs.coil.network.ktor) } androidMain.dependencies { implementation(libs.androidx.core.ktx) api(libs.kotlinx.coroutines.android) - api(libs.koin.android) } jvmMain.dependencies { diff --git a/core/common/src/androidMain/kotlin/dev/androidbroadcast/common/PlatformContext.android.kt b/core/common/src/androidMain/kotlin/dev/androidbroadcast/common/PlatformContext.android.kt new file mode 100644 index 0000000..2cefd1b --- /dev/null +++ b/core/common/src/androidMain/kotlin/dev/androidbroadcast/common/PlatformContext.android.kt @@ -0,0 +1,5 @@ +package dev.androidbroadcast.common + +import android.content.Context + +public actual typealias PlatformContext = Context diff --git a/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Named.kt b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Named.kt new file mode 100644 index 0000000..aa83553 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Named.kt @@ -0,0 +1,12 @@ +package dev.androidbroadcast.common + +import me.tatarka.inject.annotations.Qualifier + +@Qualifier +@Target( + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.FUNCTION, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.TYPE +) +public annotation class Named(val value: String) diff --git a/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/PlatformContext.kt b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/PlatformContext.kt new file mode 100644 index 0000000..1329a8b --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/PlatformContext.kt @@ -0,0 +1,3 @@ +package dev.androidbroadcast.common + +public expect abstract class PlatformContext diff --git a/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Singleton.kt b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Singleton.kt new file mode 100644 index 0000000..47ec3c9 --- /dev/null +++ b/core/common/src/commonMain/kotlin/dev/androidbroadcast/common/Singleton.kt @@ -0,0 +1,7 @@ +package dev.androidbroadcast.common + +import me.tatarka.inject.annotations.Scope + +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) +public annotation class Singleton diff --git a/core/common/src/jvmMain/kotlin/dev/androidbroadcast/common/PlatformContext.jvm.kt b/core/common/src/jvmMain/kotlin/dev/androidbroadcast/common/PlatformContext.jvm.kt new file mode 100644 index 0000000..0725ad7 --- /dev/null +++ b/core/common/src/jvmMain/kotlin/dev/androidbroadcast/common/PlatformContext.jvm.kt @@ -0,0 +1,4 @@ +package dev.androidbroadcast.common + +public actual abstract class PlatformContext { +} diff --git a/core/common/src/nativeMain/kotlin/dev/androidbroadcast/common/PlatformContext.native.kt b/core/common/src/nativeMain/kotlin/dev/androidbroadcast/common/PlatformContext.native.kt new file mode 100644 index 0000000..0725ad7 --- /dev/null +++ b/core/common/src/nativeMain/kotlin/dev/androidbroadcast/common/PlatformContext.native.kt @@ -0,0 +1,4 @@ +package dev.androidbroadcast.common + +public actual abstract class PlatformContext { +} diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index e807258..38fc91b 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -21,7 +21,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> diff --git a/core/data/src/commonMain/kotlin/dev/androidbroadcast/news/data/ArticlesRepository.kt b/core/data/src/commonMain/kotlin/dev/androidbroadcast/news/data/ArticlesRepository.kt index 139e5ce..afec9b0 100644 --- a/core/data/src/commonMain/kotlin/dev/androidbroadcast/news/data/ArticlesRepository.kt +++ b/core/data/src/commonMain/kotlin/dev/androidbroadcast/news/data/ArticlesRepository.kt @@ -1,6 +1,7 @@ package dev.androidbroadcast.news.data import dev.androidbroadcast.common.Logger +import dev.androidbroadcast.common.Singleton import dev.androidbroadcast.news.data.model.Article import dev.androidbroadcast.news.database.NewsDatabase import dev.androidbroadcast.news.database.models.ArticleDBO @@ -17,7 +18,9 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach +import me.tatarka.inject.annotations.Inject +@Inject public class ArticlesRepository( private val database: NewsDatabase, private val api: NewsApi, diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index f285335..a540b67 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -20,7 +20,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -81,7 +80,6 @@ dependencies { add("kspJvm", libs.androidx.room.compiler) add("kspAndroid", libs.androidx.room.compiler) add("kspIosSimulatorArm64", libs.androidx.room.compiler) - add("kspIosX64", libs.androidx.room.compiler) add("kspIosArm64", libs.androidx.room.compiler) // add("kspCommonMainMetadata", libs.androidx.room.compiler) } diff --git a/core/opennews-api/build.gradle.kts b/core/opennews-api/build.gradle.kts index 1dd6b46..2e69429 100644 --- a/core/opennews-api/build.gradle.kts +++ b/core/opennews-api/build.gradle.kts @@ -19,7 +19,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> diff --git a/core/platform/build.gradle.kts b/core/platform/build.gradle.kts index 9b3af52..665ac49 100644 --- a/core/platform/build.gradle.kts +++ b/core/platform/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.ksp) } kotlin { @@ -21,7 +22,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -36,8 +36,6 @@ kotlin { api(projects.core.common) implementation(libs.coil.compose) implementation(projects.core.data) - implementation(projects.core.database) - implementation(projects.core.opennewsApi) } jvmMain.dependencies { @@ -63,3 +61,10 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } } + +dependencies { + "kspAndroid"(libs.kotlinInject.compiler) + "kspIosArm64"(libs.kotlinInject.compiler) + "kspIosSimulatorArm64"(libs.kotlinInject.compiler) + "kspJvm"(libs.kotlinInject.compiler) +} diff --git a/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/AppComponent.android.kt b/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/AppComponent.android.kt new file mode 100644 index 0000000..d880fe1 --- /dev/null +++ b/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/AppComponent.android.kt @@ -0,0 +1,27 @@ +package dev.androidbroadcast.news.core + +import androidx.room.Room +import androidx.room.RoomDatabase +import dev.androidbroadcast.common.AndroidLogcatLogger +import dev.androidbroadcast.common.Logger +import dev.androidbroadcast.common.PlatformContext +import dev.androidbroadcast.news.database.NewsRoomDatabase + +internal actual fun newsRoomDatabaseBuilder(context: PlatformContext): RoomDatabase.Builder { + return Room.databaseBuilder( + context = context, + klass = NewsRoomDatabase::class.java, + name = "news" + ) +} + +internal actual fun newLogger(): Logger = AndroidLogcatLogger() + +internal actual fun createAppComponent( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext +): AppComponent { + return AppComponent::class.create(debuggable, baseUrl, apiKey, platformContext) +} diff --git a/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/KoinModules.android.kt b/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/KoinModules.android.kt deleted file mode 100644 index c7986c9..0000000 --- a/core/platform/src/androidMain/kotlin/dev/androidbroadcast/news/core/KoinModules.android.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.androidbroadcast.news.core - -import androidx.room.Room -import androidx.room.RoomDatabase -import dev.androidbroadcast.common.AndroidLogcatLogger -import dev.androidbroadcast.common.Logger -import dev.androidbroadcast.news.database.NewsRoomDatabase -import org.koin.android.ext.koin.androidContext -import org.koin.core.module.Module -import org.koin.dsl.module - -internal actual val targetKoinModule: Module = module { - - factory> { - Room.databaseBuilder( - context = androidContext(), - klass = NewsRoomDatabase::class.java, - name = "news" - ) - } - - factory { - AndroidLogcatLogger() - } -} diff --git a/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/AppComponent.kt b/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/AppComponent.kt new file mode 100644 index 0000000..be1a8cf --- /dev/null +++ b/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/AppComponent.kt @@ -0,0 +1,99 @@ +package dev.androidbroadcast.news.core + +import androidx.room.RoomDatabase +import dev.androidbroadcast.common.AppDispatchers +import dev.androidbroadcast.common.Logger +import dev.androidbroadcast.common.PlatformContext +import dev.androidbroadcast.common.Singleton +import dev.androidbroadcast.news.data.ArticlesRepository +import dev.androidbroadcast.news.database.NewsDatabase +import dev.androidbroadcast.news.database.NewsRoomDatabase +import dev.androidbroadcast.newsapi.NewsApi +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides +import kotlin.properties.Delegates.notNull + +internal expect fun createAppComponent( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext, +): AppComponent + +internal expect fun newsRoomDatabaseBuilder(context: PlatformContext): RoomDatabase.Builder + +internal expect fun newLogger(): Logger + +@Component +@Singleton +public abstract class AppComponent( + public val debuggable: Boolean, + protected val baseUrl: String, + protected val apiKey: String, + protected val platformContext: PlatformContext, +) { + + public abstract val articlesRepository: ArticlesRepository + + public abstract val newsDatabase: NewsDatabase + + @ExperimentalSerializationApi + @Singleton + @Provides + internal fun providesJson(): Json { + return Json { + isLenient = true + ignoreUnknownKeys = true + explicitNulls = false + } + } + + @Singleton + @Provides + internal fun providesNewsApi( + json: Json, + ): NewsApi { + return NewsApi( + baseUrl = baseUrl, + apiKey = apiKey, + json = json + ) + } + + @Singleton + @Provides + internal fun providesAppDispatchers(): AppDispatchers = AppDispatchers() + + @Singleton + @Provides + internal fun providesNewsDatabase( + dispatchers: AppDispatchers, + ): NewsDatabase { + return NewsDatabase( + databaseBuilder = newsRoomDatabaseBuilder(platformContext), + dispatcher = dispatchers.io + ) + } + + @Singleton + @Provides + internal fun providesLogger(): Logger = newLogger() + + public companion object { + + public var appComponent: AppComponent by notNull() + private set + + internal fun create( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext, + ): AppComponent { + appComponent = createAppComponent(debuggable, baseUrl, apiKey, platformContext) + return appComponent + } + } +} diff --git a/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/KoinModules.kt b/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/KoinModules.kt deleted file mode 100644 index cf2fe46..0000000 --- a/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/KoinModules.kt +++ /dev/null @@ -1,48 +0,0 @@ -package dev.androidbroadcast.news.core - -import androidx.room.RoomDatabase -import dev.androidbroadcast.common.AppDispatchers -import dev.androidbroadcast.news.data.ArticlesRepository -import dev.androidbroadcast.news.database.NewsDatabase -import dev.androidbroadcast.news.database.NewsRoomDatabase -import dev.androidbroadcast.newsapi.NewsApi -import kotlinx.serialization.json.Json -import org.koin.core.module.Module -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -/** - * Koin Module with target platform specifics dependencies - */ -internal expect val targetKoinModule: Module - -internal val appKoinModule: Module = - module { - single { - Json { - isLenient = true - ignoreUnknownKeys = true - explicitNulls = false - } - } - - single { - NewsApi( - baseUrl = getProperty(ConfigProperties.NewsApi.BaseUrl), - apiKey = getProperty(ConfigProperties.NewsApi.ApiKey), - json = get() - ) - } - - single { AppDispatchers() } - - single { - val dispatchers = get() - NewsDatabase( - databaseBuilder = get>(), - dispatcher = dispatchers.io - ) - } - - singleOf(::ArticlesRepository) - } diff --git a/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/NewsAppPlatform.kt b/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/NewsAppPlatform.kt index 7ee74b4..760c368 100644 --- a/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/NewsAppPlatform.kt +++ b/core/platform/src/commonMain/kotlin/dev/androidbroadcast/news/core/NewsAppPlatform.kt @@ -1,54 +1,29 @@ package dev.androidbroadcast.news.core import coil3.ImageLoader -import coil3.PlatformContext import coil3.SingletonImageLoader -import org.koin.core.component.KoinComponent -import org.koin.core.component.KoinScopeComponent -import org.koin.core.context.startKoin -import org.koin.core.logger.Level -import org.koin.core.scope.Scope -import org.koin.dsl.KoinAppDeclaration +import dev.androidbroadcast.common.PlatformContext +import kotlin.properties.Delegates.notNull public class NewsAppPlatform : - KoinComponent, - KoinScopeComponent, SingletonImageLoader.Factory { - override val scope: Scope - // Root scope id taken from Koin source code - get() = getKoin().getScope("_root_") + + public var appComponent: AppComponent by notNull() + private set public fun start( debug: Boolean, newsApiKey: String, newsApiBaseUrl: String, - targetAppDeclaration: KoinAppDeclaration = {} + platformContext: PlatformContext, ) { - startKoin { - modules( - appKoinModule, - targetKoinModule - ) - - properties( - mapOf( - ConfigProperties.NewsPlatform.Debug to debug, - ConfigProperties.NewsApi.ApiKey to newsApiKey, - ConfigProperties.NewsApi.BaseUrl to newsApiBaseUrl - ) - ) - - if (debug) { - printLogger(Level.DEBUG) - } - - targetAppDeclaration() - } + appComponent = AppComponent.create( + debug, newsApiBaseUrl, newsApiKey, + platformContext + ) } - override fun newImageLoader(context: PlatformContext): ImageLoader = - newImageLoader( - context, - debug = getKoin().getProperty(ConfigProperties.NewsPlatform.Debug, false) - ) + override fun newImageLoader(context: coil3.PlatformContext): ImageLoader { + return newImageLoader(context) + } } diff --git a/core/platform/src/iosArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosArm64.kt b/core/platform/src/iosArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosArm64.kt new file mode 100644 index 0000000..a1b6d74 --- /dev/null +++ b/core/platform/src/iosArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosArm64.kt @@ -0,0 +1,12 @@ +package dev.androidbroadcast.news.core + +import dev.androidbroadcast.common.PlatformContext + +internal actual fun createAppComponent( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext +): AppComponent { + return AppComponent::class.create(debuggable, baseUrl, apiKey, platformContext) +} diff --git a/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/AppComponent.ios.kt b/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/AppComponent.ios.kt new file mode 100644 index 0000000..fe63129 --- /dev/null +++ b/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/AppComponent.ios.kt @@ -0,0 +1,20 @@ +package dev.androidbroadcast.news.core + +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import dev.androidbroadcast.common.Logger +import dev.androidbroadcast.common.PlatformContext +import dev.androidbroadcast.common.PrintLogger +import dev.androidbroadcast.news.database.NewsRoomDatabase +import dev.androidbroadcast.news.database.instantiateNewsRoomDatabase +import platform.Foundation.NSHomeDirectory + +internal actual fun newsRoomDatabaseBuilder(context: PlatformContext): RoomDatabase.Builder { + return Room.databaseBuilder( + name = "${NSHomeDirectory()}/news.db", + factory = { instantiateNewsRoomDatabase() } + ).setDriver(BundledSQLiteDriver()) +} + +internal actual fun newLogger(): Logger = PrintLogger() diff --git a/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/KoinModules.ios.kt b/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/KoinModules.ios.kt deleted file mode 100644 index abddf1a..0000000 --- a/core/platform/src/iosMain/kotlin/dev/androidbroadcast/news/core/KoinModules.ios.kt +++ /dev/null @@ -1,28 +0,0 @@ -package dev.androidbroadcast.news.core - -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.androidbroadcast.common.Logger -import dev.androidbroadcast.common.PrintLogger -import dev.androidbroadcast.news.database.NewsRoomDatabase -import dev.androidbroadcast.news.database.instantiateNewsRoomDatabase -import org.koin.core.module.Module -import org.koin.dsl.module -import platform.Foundation.NSHomeDirectory - -/** - * Koin Module with target platform specifics dependencies - */ -internal actual val targetKoinModule: Module = module { - factory> { - Room.databaseBuilder( - name = "${NSHomeDirectory()}/news.db", - factory = { instantiateNewsRoomDatabase() } - ).setDriver(BundledSQLiteDriver()) - } - - factory { - PrintLogger() - } -} diff --git a/core/platform/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosSimulatorArm64.kt b/core/platform/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosSimulatorArm64.kt new file mode 100644 index 0000000..a1b6d74 --- /dev/null +++ b/core/platform/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/core/AppComponent.iosSimulatorArm64.kt @@ -0,0 +1,12 @@ +package dev.androidbroadcast.news.core + +import dev.androidbroadcast.common.PlatformContext + +internal actual fun createAppComponent( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext +): AppComponent { + return AppComponent::class.create(debuggable, baseUrl, apiKey, platformContext) +} diff --git a/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/AppComponent.jvm.kt b/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/AppComponent.jvm.kt new file mode 100644 index 0000000..7200446 --- /dev/null +++ b/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/AppComponent.jvm.kt @@ -0,0 +1,27 @@ +package dev.androidbroadcast.news.core + +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import dev.androidbroadcast.common.Logger +import dev.androidbroadcast.common.PlatformContext +import dev.androidbroadcast.common.PrintLogger +import dev.androidbroadcast.news.database.NewsRoomDatabase +import java.io.File + +internal actual fun newsRoomDatabaseBuilder(context: PlatformContext): RoomDatabase.Builder { + val dbFile = File(System.getProperty("java.io.tmpdir"), "dev.androidbroadcast.news.db") + return Room.databaseBuilder(name = dbFile.absolutePath) + .setDriver(BundledSQLiteDriver()) +} + +internal actual fun newLogger(): Logger = PrintLogger() + +internal actual fun createAppComponent( + debuggable: Boolean, + baseUrl: String, + apiKey: String, + platformContext: PlatformContext +): AppComponent { + return AppComponent::class.create(debuggable, baseUrl, apiKey, platformContext) +} diff --git a/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/KoinModules.jvm.kt b/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/KoinModules.jvm.kt deleted file mode 100644 index f4867fc..0000000 --- a/core/platform/src/jvmMain/kotlin/dev/androidbroadcast/news/core/KoinModules.jvm.kt +++ /dev/null @@ -1,27 +0,0 @@ -package dev.androidbroadcast.news.core - -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import dev.androidbroadcast.common.Logger -import dev.androidbroadcast.common.PrintLogger -import dev.androidbroadcast.news.database.NewsRoomDatabase -import org.koin.core.module.Module -import org.koin.dsl.module -import java.io.File - -/** - * Koin Module with target platform specifics dependencies - */ -internal actual val targetKoinModule: Module = module { - - factory> { - val dbFile = File(System.getProperty("java.io.tmpdir"), "dev.androidbroadcast.news.db") - Room.databaseBuilder(name = dbFile.absolutePath) - .setDriver(BundledSQLiteDriver()) - } - - factory { - PrintLogger() - } -} diff --git a/core/uikit/build.gradle.kts b/core/uikit/build.gradle.kts index 4fc49cf..7fb06c4 100644 --- a/core/uikit/build.gradle.kts +++ b/core/uikit/build.gradle.kts @@ -21,7 +21,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> diff --git a/desktop/src/main/kotlin/dev/androidbroadcast/news/NewsAppDesktop.kt b/desktop/src/main/kotlin/dev/androidbroadcast/news/NewsAppDesktop.kt index 77635f8..ac62ed3 100644 --- a/desktop/src/main/kotlin/dev/androidbroadcast/news/NewsAppDesktop.kt +++ b/desktop/src/main/kotlin/dev/androidbroadcast/news/NewsAppDesktop.kt @@ -2,6 +2,7 @@ package dev.androidbroadcast.news import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import dev.androidbroadcast.common.PlatformContext import dev.androidbroadcast.news.compose.NewsApp import dev.androidbroadcast.news.core.NewsAppPlatform @@ -10,7 +11,8 @@ fun main() { platform.start( debug = true, newsApiKey = "155ae65d7264461397c901103488c01e", - newsApiBaseUrl = "https://newsapi.org/v2/" + newsApiBaseUrl = "https://newsapi.org/v2/", + platformContext = object : PlatformContext() {}, ) application { diff --git a/features/news-main/ui-logic/build.gradle.kts b/features/news-main/ui-logic/build.gradle.kts index d5f7164..1584af4 100644 --- a/features/news-main/ui-logic/build.gradle.kts +++ b/features/news-main/ui-logic/build.gradle.kts @@ -7,10 +7,11 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.ksp) } kotlin { - explicitApi = ExplicitApiMode.Strict +// explicitApi = ExplicitApiMode.Strict androidTarget { compilerOptions { @@ -21,7 +22,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -35,15 +35,16 @@ kotlin { commonMain.dependencies { api(projects.core.data) api(projects.core.common) + api(projects.core.database) + api(projects.core.opennewsApi) implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.viewmodel) - api(libs.koin.compose.viewmodel) + + implementation(projects.core.platform) } androidMain.dependencies { implementation(libs.androidx.core.ktx) - api(libs.koin.androidx.compose) - api(libs.koin.android) } } } @@ -72,3 +73,10 @@ configurations.all { } } } + +dependencies { + "kspAndroid"(libs.kotlinInject.compiler) + "kspIosArm64"(libs.kotlinInject.compiler) + "kspIosSimulatorArm64"(libs.kotlinInject.compiler) + "kspJvm"(libs.kotlinInject.compiler) +} diff --git a/features/news-main/ui-logic/src/androidMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.android.kt b/features/news-main/ui-logic/src/androidMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.android.kt new file mode 100644 index 0000000..8e44b2f --- /dev/null +++ b/features/news-main/ui-logic/src/androidMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.android.kt @@ -0,0 +1,5 @@ +package dev.androidbroadcast.news.main.di + +actual fun createNewsMainComponent(deps: NewsMainComponent.Deps): NewsMainComponent { + return NewsMainComponent::class.create(deps) +} diff --git a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/GetAllArticlesUseCase.kt b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/GetAllArticlesUseCase.kt index 7839205..779e658 100644 --- a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/GetAllArticlesUseCase.kt +++ b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/GetAllArticlesUseCase.kt @@ -5,7 +5,9 @@ import dev.androidbroadcast.news.data.RequestResult import dev.androidbroadcast.news.data.map import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import me.tatarka.inject.annotations.Inject +@Inject internal class GetAllArticlesUseCase( private val repository: ArticlesRepository ) { diff --git a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/KoinModule.kt b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/KoinModule.kt deleted file mode 100644 index c574deb..0000000 --- a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/KoinModule.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.androidbroadcast.news.main - -import org.koin.compose.viewmodel.dsl.viewModelOf -import org.koin.core.module.Module -import org.koin.core.module.dsl.factoryOf -import org.koin.dsl.module - -public val featuresNewsMainUiLogicKoinModule: Module = module { - - factoryOf(::GetAllArticlesUseCase) - - viewModelOf(::NewsMainViewModel) -} diff --git a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainViewModel.kt b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainViewModel.kt index 713d5ee..de37b6b 100644 --- a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainViewModel.kt +++ b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainViewModel.kt @@ -7,15 +7,15 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import me.tatarka.inject.annotations.Inject -public class NewsMainViewModel internal constructor() : ViewModel(), KoinComponent { - - private val getAllArticlesUseCase: GetAllArticlesUseCase by inject() +@Inject +public class NewsMainViewModel internal constructor( + private val getAllArticlesUseCase: Lazy +) : ViewModel() { public val state: StateFlow = - getAllArticlesUseCase.invoke(query = "android") + getAllArticlesUseCase.value.invoke(query = "android") .map(RequestResult>::toState) .stateIn(viewModelScope, SharingStarted.Lazily, State.None) } diff --git a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.kt b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.kt new file mode 100644 index 0000000..0e7427b --- /dev/null +++ b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.kt @@ -0,0 +1,21 @@ +package dev.androidbroadcast.news.main.di + +import dev.androidbroadcast.news.data.ArticlesRepository +import dev.androidbroadcast.news.main.NewsMainViewModel +import me.tatarka.inject.annotations.Component + +public expect fun createNewsMainComponent(deps: NewsMainComponent.Deps): NewsMainComponent + +@Component +@NewsMainScope +public abstract class NewsMainComponent( + @Component val deps: Deps +) { + + abstract val newsMainViewModel: NewsMainViewModel + + interface Deps { + + val articlesRepository: ArticlesRepository + } +} diff --git a/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainScope.kt b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainScope.kt new file mode 100644 index 0000000..8724ae7 --- /dev/null +++ b/features/news-main/ui-logic/src/commonMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainScope.kt @@ -0,0 +1,7 @@ +package dev.androidbroadcast.news.main.di + +import me.tatarka.inject.annotations.Scope + +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) +public annotation class NewsMainScope diff --git a/features/news-main/ui-logic/src/iosArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosArm64.kt b/features/news-main/ui-logic/src/iosArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosArm64.kt new file mode 100644 index 0000000..8e44b2f --- /dev/null +++ b/features/news-main/ui-logic/src/iosArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosArm64.kt @@ -0,0 +1,5 @@ +package dev.androidbroadcast.news.main.di + +actual fun createNewsMainComponent(deps: NewsMainComponent.Deps): NewsMainComponent { + return NewsMainComponent::class.create(deps) +} diff --git a/features/news-main/ui-logic/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosSimulatorArm64.kt b/features/news-main/ui-logic/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosSimulatorArm64.kt new file mode 100644 index 0000000..8e44b2f --- /dev/null +++ b/features/news-main/ui-logic/src/iosSimulatorArm64Main/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.iosSimulatorArm64.kt @@ -0,0 +1,5 @@ +package dev.androidbroadcast.news.main.di + +actual fun createNewsMainComponent(deps: NewsMainComponent.Deps): NewsMainComponent { + return NewsMainComponent::class.create(deps) +} diff --git a/features/news-main/ui-logic/src/jvmMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.jvm.kt b/features/news-main/ui-logic/src/jvmMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.jvm.kt new file mode 100644 index 0000000..8e44b2f --- /dev/null +++ b/features/news-main/ui-logic/src/jvmMain/kotlin/dev/androidbroadcast/news/main/di/NewsMainComponent.jvm.kt @@ -0,0 +1,5 @@ +package dev.androidbroadcast.news.main.di + +actual fun createNewsMainComponent(deps: NewsMainComponent.Deps): NewsMainComponent { + return NewsMainComponent::class.create(deps) +} diff --git a/features/news-main/ui/build.gradle.kts b/features/news-main/ui/build.gradle.kts index 268089b..b41b631 100644 --- a/features/news-main/ui/build.gradle.kts +++ b/features/news-main/ui/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.jetbrainsCompose) alias(libs.plugins.detekt) + alias(libs.plugins.ksp) } kotlin { @@ -21,7 +22,6 @@ kotlin { jvm() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -38,12 +38,13 @@ kotlin { implementation(libs.coil.compose) implementation(projects.core.uikit) - implementation(libs.koin.compose) - implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.jetbrains.lifecycle.viewmodel.compose) implementation(compose.components.resources) + + implementation(projects.core.platform) } androidMain.dependencies { @@ -84,4 +85,13 @@ composeCompiler { dependencies { debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling.preview) + + // 1. Configure code generation into the common source set +// kspCommonMainMetadata(libs.kotlinInject.compiler) + + // 2. Configure code generation into each KMP target source set + "kspAndroid"(libs.kotlinInject.compiler) + "kspIosArm64"(libs.kotlinInject.compiler) + "kspIosSimulatorArm64"(libs.kotlinInject.compiler) + "kspJvm"(libs.kotlinInject.compiler) } diff --git a/features/news-main/ui/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainFeatureUI.kt b/features/news-main/ui/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainFeatureUI.kt index cc97713..5d6153d 100644 --- a/features/news-main/ui/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainFeatureUI.kt +++ b/features/news-main/ui/src/commonMain/kotlin/dev/androidbroadcast/news/main/NewsMainFeatureUI.kt @@ -1,5 +1,3 @@ -@file:OptIn(KoinExperimentalAPI::class) - package dev.androidbroadcast.news.main import androidx.compose.foundation.background @@ -12,22 +10,34 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import dev.androidbroadcast.news.NewsTheme -import org.koin.compose.module.rememberKoinModules -import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.annotation.KoinExperimentalAPI +import dev.androidbroadcast.news.core.AppComponent +import dev.androidbroadcast.news.data.ArticlesRepository +import dev.androidbroadcast.news.main.di.NewsMainComponent +import dev.androidbroadcast.news.main.di.createNewsMainComponent + +private class NewsMainComponentDepsImpl( + private val appComponent: AppComponent +) : NewsMainComponent.Deps { + + override val articlesRepository: ArticlesRepository + get() = appComponent.articlesRepository +} @Composable fun NewsMainScreen(modifier: Modifier = Modifier) { - rememberKoinModules { - listOf( - featuresNewsMainUiLogicKoinModule, - ) + val component: NewsMainComponent = remember { + createNewsMainComponent(NewsMainComponentDepsImpl(AppComponent.appComponent)) } - NewsMainScreen(viewModel = koinViewModel(), modifier = modifier) + NewsMainScreen( + viewModel = viewModel { component.newsMainViewModel }, + modifier = modifier + ) } @Composable diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b10e363..997f2a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,14 @@ [versions] agp = "8.3.2" annotation = "1.8.2" -kotlin = "2.0.20" -kotlin-serialization-json = "1.6.3" +kotlin = "2.0.21-RC" +kotlin-serialization-json = "1.7.1" coreKtx = "1.13.1" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" androidx-lifecycle = "2.8.6" +jetbrains-lifecycle = "2.8.0" activityCompose = "1.9.2" composeBom = "2024.09.03" androidx-compose-runtime = "1.7.3" @@ -15,7 +16,7 @@ kotlinx-coroutines = "1.9.0" appcompat = "1.7.0" material = "1.12.0" room = "2.7.0-alpha09" -ksp = "2.0.20-1.0.25" +ksp = "2.0.21-RC-1.0.25" okhttp = "4.12.0" coil = "3.0.0-alpha06" detekt = "1.23.6" @@ -31,9 +32,8 @@ kotlinx-datetime = "0.6.0" compose-rules = "0.4.3" compose-plugin = "1.6.11" ktor = "2.3.11" -koin = "3.6.0-Beta4" -koin-compose = "1.2.0-Beta4" sqlite = "2.5.0-alpha09" +kotlinInject = "0.7.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -60,6 +60,8 @@ androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle- androidx-lifecycle-viewmodel-compose-android = { module = "androidx.lifecycle:lifecycle-viewmodel-compose-android", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel-compose-desktop = { module = "androidx.lifecycle:lifecycle-viewmodel-compose-desktop", version.ref = "androidx-lifecycle" } +jetbrains-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "jetbrains-lifecycle" } + androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } @@ -101,11 +103,8 @@ androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profi detekt-rules-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "compose-rules" } -koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = 'koin' } -koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = 'koin' } -koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = 'koin' } -koin-compose = { group = "io.insert-koin", name = "koin-compose", version.ref = 'koin-compose' } -koin-compose-viewmodel = { group = "io.insert-koin", name = "koin-compose-viewmodel", version.ref = 'koin-compose' } +kotlinInject-compiler = { group = "me.tatarka.inject", name = "kotlin-inject-compiler-ksp", version.ref = 'kotlinInject' } +kotlinInject-runtime = { group = "me.tatarka.inject", name = "kotlin-inject-runtime-kmp", version.ref = 'kotlinInject' } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index a580b03..95c8b35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,7 +29,7 @@ dependencyResolutionManagement { } rootProject.name = "NewsSearchApp" -include(":app") +//include(":app") include(":core:opennews-api") include(":core:database") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 49b56ea..949a24f 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -7,7 +7,6 @@ plugins { kotlin { listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> diff --git a/shared/src/iosMain/kotlin/dev.androidbroadcast.news.compose/NewsViewController.kt b/shared/src/iosMain/kotlin/dev.androidbroadcast.news.compose/NewsViewController.kt index 56e6262..e9d0e5b 100644 --- a/shared/src/iosMain/kotlin/dev.androidbroadcast.news.compose/NewsViewController.kt +++ b/shared/src/iosMain/kotlin/dev.androidbroadcast.news.compose/NewsViewController.kt @@ -1,4 +1,5 @@ import androidx.compose.ui.window.ComposeUIViewController +import dev.androidbroadcast.common.PlatformContext import dev.androidbroadcast.news.compose.NewsApp import dev.androidbroadcast.news.core.NewsAppPlatform import platform.UIKit.UIViewController @@ -9,7 +10,8 @@ fun MainViewController(): UIViewController { appPlatform.start( debug = true, newsApiKey = "155ae65d7264461397c901103488c01e", - newsApiBaseUrl = "https://newsapi.org/v2/" + newsApiBaseUrl = "https://newsapi.org/v2/", + platformContext = object : PlatformContext() {} ) return ComposeUIViewController { NewsApp() } }