refactor: Use Koin

An experiment, aims to ditch Injekt and replace it with Koin while providing Injekt API facade for extensions
This commit is contained in:
Ahmad Ansori Palembani 2024-09-18 11:14:23 +07:00
parent 19f6b26567
commit 8a4ec76b32
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
13 changed files with 266 additions and 192 deletions

View file

@ -59,13 +59,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import org.koin.core.context.startKoin
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.core.CrashlyticsLogWriter import yokai.core.CrashlyticsLogWriter
import yokai.core.di.AppModule import yokai.core.di.appModule
import yokai.core.di.DomainModule import yokai.core.di.domainModule
import yokai.core.di.PreferenceModule import yokai.core.di.preferenceModule
import yokai.core.migration.Migrator import yokai.core.migration.Migrator
import yokai.core.migration.migrations.migrations import yokai.core.migration.migrations.migrations
import yokai.domain.base.BasePreferences import yokai.domain.base.BasePreferences
@ -97,10 +98,8 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
if (packageName != process) WebView.setDataDirectorySuffix(process) if (packageName != process) WebView.setDataDirectorySuffix(process)
} }
Injekt.apply { startKoin {
importModule(PreferenceModule(this@App)) modules(preferenceModule(this@App), appModule(this@App), domainModule())
importModule(AppModule(this@App))
importModule(DomainModule())
} }
basePreferences.crashReport().changes() basePreferences.crashReport().changes()

View file

@ -1,7 +1,6 @@
package yokai.core.di package yokai.core.di
import android.app.Application import android.app.Application
import androidx.core.content.ContextCompat
import androidx.sqlite.db.SupportSQLiteOpenHelper import androidx.sqlite.db.SupportSQLiteOpenHelper
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver
@ -27,23 +26,19 @@ import kotlinx.serialization.json.Json
import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import uy.kohesive.injekt.api.InjektModule import org.koin.core.module.dsl.createdAtStart
import uy.kohesive.injekt.api.InjektRegistrar import org.koin.core.module.dsl.withOptions
import uy.kohesive.injekt.api.addSingleton import org.koin.dsl.module
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
import yokai.data.AndroidDatabaseHandler import yokai.data.AndroidDatabaseHandler
import yokai.data.Database import yokai.data.Database
import yokai.data.DatabaseHandler import yokai.data.DatabaseHandler
import yokai.domain.SplashState import yokai.domain.SplashState
import yokai.domain.storage.StorageManager import yokai.domain.storage.StorageManager
class AppModule(val app: Application) : InjektModule { fun appModule(app: Application) = module {
single { app }
override fun InjektRegistrar.registerInjectables() { single<SupportSQLiteOpenHelper> {
addSingleton(app)
addSingletonFactory<SupportSQLiteOpenHelper> {
val configuration = SupportSQLiteOpenHelper.Configuration.builder(app) val configuration = SupportSQLiteOpenHelper.Configuration.builder(app)
.callback(DbOpenCallback()) .callback(DbOpenCallback())
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@ -61,7 +56,7 @@ class AppModule(val app: Application) : InjektModule {
RequerySQLiteOpenHelperFactory().create(configuration) RequerySQLiteOpenHelperFactory().create(configuration)
} }
addSingletonFactory<SqlDriver> { single<SqlDriver> {
AndroidSqliteDriver(openHelper = get()) AndroidSqliteDriver(openHelper = get())
/* /*
AndroidSqliteDriver( AndroidSqliteDriver(
@ -78,20 +73,26 @@ class AppModule(val app: Application) : InjektModule {
) )
*/ */
} }
addSingletonFactory {
single {
Database( Database(
driver = get(), driver = get(),
) )
} withOptions {
createdAtStart()
} }
addSingletonFactory<DatabaseHandler> { AndroidDatabaseHandler(get(), get()) }
addSingletonFactory { DatabaseHelper(app, get()) } single<DatabaseHandler> { AndroidDatabaseHandler(get(), get()) }
addSingletonFactory { ChapterCache(app) } single { DatabaseHelper(app, get()) } withOptions {
createdAtStart()
}
addSingletonFactory { CoverCache(app) } single { ChapterCache(app) }
addSingletonFactory { single { CoverCache(app) }
single {
NetworkHelper( NetworkHelper(
app, app,
get(), get(),
@ -107,26 +108,34 @@ class AppModule(val app: Application) : InjektModule {
) )
} }
} }
} withOptions {
createdAtStart()
} }
addSingletonFactory { JavaScriptEngine(app) } single { JavaScriptEngine(app) }
addSingletonFactory { SourceManager(app, get()) } single { SourceManager(app, get()) } withOptions {
addSingletonFactory { ExtensionManager(app) } createdAtStart()
}
single { ExtensionManager(app) }
addSingletonFactory { DownloadManager(app) } single { DownloadManager(app) } withOptions {
createdAtStart()
}
addSingletonFactory { CustomMangaManager(app) } single { CustomMangaManager(app) } withOptions {
createdAtStart()
}
addSingletonFactory { TrackManager(app) } single { TrackManager(app) }
addSingletonFactory { single {
Json { Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
explicitNulls = false explicitNulls = false
} }
} }
addSingletonFactory { single {
XML { XML {
defaultPolicy { defaultPolicy {
ignoreUnknownChildren() ignoreUnknownChildren()
@ -138,28 +147,12 @@ class AppModule(val app: Application) : InjektModule {
} }
} }
addSingletonFactory { ChapterFilter() } single { ChapterFilter() }
addSingletonFactory { MangaShortcutManager() } single { MangaShortcutManager() }
addSingletonFactory { AndroidStorageFolderProvider(app) } single { AndroidStorageFolderProvider(app) }
addSingletonFactory { StorageManager(app, get()) } single { StorageManager(app, get()) }
addSingletonFactory { SplashState() } single { SplashState() }
// Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute {
get<NetworkHelper>()
get<SourceManager>()
get<Database>()
get<DatabaseHelper>()
get<DownloadManager>()
get<CustomMangaManager>()
}
}
} }

View file

@ -1,10 +1,6 @@
package yokai.core.di package yokai.core.di
import uy.kohesive.injekt.api.InjektModule import org.koin.dsl.module
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
import yokai.data.category.CategoryRepositoryImpl import yokai.data.category.CategoryRepositoryImpl
import yokai.data.chapter.ChapterRepositoryImpl import yokai.data.chapter.ChapterRepositoryImpl
import yokai.data.extension.repo.ExtensionRepoRepositoryImpl import yokai.data.extension.repo.ExtensionRepoRepositoryImpl
@ -18,7 +14,6 @@ import yokai.domain.chapter.interactor.DeleteChapter
import yokai.domain.chapter.interactor.GetAvailableScanlators import yokai.domain.chapter.interactor.GetAvailableScanlators
import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.InsertChapter import yokai.domain.chapter.interactor.InsertChapter
import yokai.domain.recents.interactor.GetRecents
import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.extension.interactor.TrustExtension import yokai.domain.extension.interactor.TrustExtension
import yokai.domain.extension.repo.ExtensionRepoRepository import yokai.domain.extension.repo.ExtensionRepoRepository
@ -39,43 +34,42 @@ import yokai.domain.manga.interactor.GetLibraryManga
import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.recents.interactor.GetRecents
class DomainModule : InjektModule { fun domainModule() = module {
override fun InjektRegistrar.registerInjectables() { factory { TrustExtension(get(), get()) }
addFactory { TrustExtension(get(), get()) }
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) } single<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
addFactory { CreateExtensionRepo(get()) } factory { CreateExtensionRepo(get()) }
addFactory { DeleteExtensionRepo(get()) } factory { DeleteExtensionRepo(get()) }
addFactory { GetExtensionRepo(get()) } factory { GetExtensionRepo(get()) }
addFactory { GetExtensionRepoCount(get()) } factory { GetExtensionRepoCount(get()) }
addFactory { ReplaceExtensionRepo(get()) } factory { ReplaceExtensionRepo(get()) }
addFactory { UpdateExtensionRepo(get(), get()) } factory { UpdateExtensionRepo(get(), get()) }
addSingletonFactory<CustomMangaRepository> { CustomMangaRepositoryImpl(get()) } single<CustomMangaRepository> { CustomMangaRepositoryImpl(get()) }
addFactory { CreateCustomManga(get()) } factory { CreateCustomManga(get()) }
addFactory { DeleteCustomManga(get()) } factory { DeleteCustomManga(get()) }
addFactory { GetCustomManga(get()) } factory { GetCustomManga(get()) }
addFactory { RelinkCustomManga(get()) } factory { RelinkCustomManga(get()) }
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } single<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetManga(get()) } factory { GetManga(get()) }
addFactory { GetLibraryManga(get()) } factory { GetLibraryManga(get()) }
addFactory { InsertManga(get()) } factory { InsertManga(get()) }
addFactory { UpdateManga(get()) } factory { UpdateManga(get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } single<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { DeleteChapter(get()) } factory { DeleteChapter(get()) }
addFactory { GetAvailableScanlators(get()) } factory { GetAvailableScanlators(get()) }
addFactory { GetChapter(get()) } factory { GetChapter(get()) }
addFactory { InsertChapter(get()) } factory { InsertChapter(get()) }
addFactory { UpdateChapter(get()) } factory { UpdateChapter(get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } single<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetRecents(get(), get()) } factory { GetRecents(get(), get()) }
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) } single<CategoryRepository> { CategoryRepositoryImpl(get()) }
addFactory { GetCategories(get()) } factory { GetCategories(get()) }
}
} }

View file

@ -9,10 +9,7 @@ import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackPreferences import eu.kanade.tachiyomi.data.track.TrackPreferences
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import uy.kohesive.injekt.api.InjektModule import org.koin.dsl.module
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
import yokai.domain.backup.BackupPreferences import yokai.domain.backup.BackupPreferences
import yokai.domain.base.BasePreferences import yokai.domain.base.BasePreferences
import yokai.domain.download.DownloadPreferences import yokai.domain.download.DownloadPreferences
@ -22,47 +19,45 @@ import yokai.domain.storage.StoragePreferences
import yokai.domain.ui.UiPreferences import yokai.domain.ui.UiPreferences
import yokai.domain.ui.settings.ReaderPreferences import yokai.domain.ui.settings.ReaderPreferences
class PreferenceModule(val application: Application) : InjektModule { fun preferenceModule(application: Application) = module {
override fun InjektRegistrar.registerInjectables() { single<PreferenceStore> { AndroidPreferenceStore(application) }
addSingletonFactory<PreferenceStore> { AndroidPreferenceStore(application) }
addSingletonFactory { BasePreferences(get()) } single { BasePreferences(get()) }
addSingletonFactory { SourcePreferences(get()) } single { SourcePreferences(get()) }
addSingletonFactory { TrackPreferences(get()) } single { TrackPreferences(get()) }
addSingletonFactory { UiPreferences(get()) } single { UiPreferences(get()) }
addSingletonFactory { ReaderPreferences(get()) } single { ReaderPreferences(get()) }
addSingletonFactory { RecentsPreferences(get()) } single { RecentsPreferences(get()) }
addSingletonFactory { DownloadPreferences(get()) } single { DownloadPreferences(get()) }
addSingletonFactory { single {
NetworkPreferences( NetworkPreferences(
get(), get(),
BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY, BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY,
) )
} }
addSingletonFactory { SecurityPreferences(get()) } single { SecurityPreferences(get()) }
addSingletonFactory { BackupPreferences(get()) } single { BackupPreferences(get()) }
addSingletonFactory { single {
PreferencesHelper( PreferencesHelper(
context = application, context = application,
preferenceStore = get(), preferenceStore = get(),
) )
} }
addSingletonFactory { single {
StoragePreferences( StoragePreferences(
folderProvider = get<AndroidStorageFolderProvider>(), folderProvider = get<AndroidStorageFolderProvider>(),
preferenceStore = get(), preferenceStore = get(),
) )
} }
} }
}

View file

@ -5,6 +5,6 @@ import uy.kohesive.injekt.Injekt
class MigrationContext(val dryRun: Boolean) { class MigrationContext(val dryRun: Boolean) {
inline fun <reified T> get(): T? { inline fun <reified T> get(): T? {
return Injekt.getInstanceOrNull(T::class.java) return Injekt.getInstanceOrNull(T::class)
} }
} }

View file

@ -33,7 +33,7 @@ kotlin {
androidMain { androidMain {
dependencies { dependencies {
// Dependency injection // Dependency injection
api(libs.injekt.core) api(projects.injektKoin)
// Network client // Network client
api(libs.okhttp) api(libs.okhttp)

View file

@ -12,6 +12,7 @@ sqlite = "2.4.0"
sqldelight = "2.0.2" sqldelight = "2.0.2"
junit = "5.8.2" junit = "5.8.2"
kermit = "2.0.4" kermit = "2.0.4"
koin = "4.0.0"
voyager = "1.1.0-beta02" voyager = "1.1.0-beta02"
[libraries] [libraries]
@ -45,6 +46,9 @@ injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "41
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core" }
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version = "5.9.1" } kotest-assertions = { module = "io.kotest:kotest-assertions-core", version = "5.9.1" }
libarchive = { module = "me.zhanghai.android.libarchive:library", version = "1.1.0" } libarchive = { module = "me.zhanghai.android.libarchive:library", version = "1.1.0" }

View file

@ -0,0 +1,38 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(androidx.plugins.library)
alias(kotlinx.plugins.multiplatform)
alias(kotlinx.plugins.serialization)
}
kotlin {
androidTarget()
sourceSets {
val commonMain by getting {
dependencies {
api(project.dependencies.platform(libs.koin.bom))
api(libs.koin.core)
}
}
val androidMain by getting {
dependencies {
}
}
}
}
android {
namespace = "uy.kohesive.injekt"
defaultConfig {
consumerProguardFile("consumer-proguard.pro")
}
}
tasks {
withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.addAll(
"-Xexpect-actual-classes",
)
}
}

View file

@ -0,0 +1 @@
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }

View file

@ -0,0 +1,37 @@
package uy.kohesive.injekt
import kotlin.reflect.KClass
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.mp.KoinPlatformTools
/**
* Injekt facade to provide Injekt API for Extensions
*/
interface Injekt {
companion object {
@Suppress("unused")
fun <T> getInstanceOrNull(
clazz: KClass<*>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null,
): T? = KoinPlatformTools.defaultContext().getOrNull()?.getOrNull(clazz, qualifier, parameters)
}
}
inline fun <reified T : Any> getKoinInstance(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T = KoinPlatformTools.defaultContext().get().get<T>(qualifier, parameters)
inline fun <reified T : Any> getKoinInstanceOrNull(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T? = KoinPlatformTools.defaultContext().getOrNull()?.getOrNull<T>(qualifier, parameters)
@Suppress("unused")
inline fun <reified T : Any> injectLazy(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<T> = lazy(mode) { getKoinInstance<T>(qualifier, parameters) }

View file

@ -0,0 +1,12 @@
package uy.kohesive.injekt.api
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.getKoinInstance
@Suppress("unused")
inline fun <reified T : Any> Injekt.Companion.get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T = getKoinInstance<T>(qualifier, parameters)

View file

@ -35,6 +35,7 @@ include(":core")
include(":data") include(":data")
include(":domain") include(":domain")
include(":i18n") include(":i18n")
include(":injekt-koin")
include(":presentation:core") include(":presentation:core")
//include(":presentation:widget") //include(":presentation:widget")
include(":source:api") include(":source:api")

View file

@ -12,7 +12,7 @@ kotlin {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
api(kotlinx.serialization.json) api(kotlinx.serialization.json)
api(libs.injekt.core) api(projects.injektKoin)
api(libs.rxjava) api(libs.rxjava)
api(libs.jsoup) api(libs.jsoup)
} }