From e0adb2e8f756adebb7fa0c13047799810fd28717 Mon Sep 17 00:00:00 2001 From: Arnaud Giuliani Date: Mon, 16 Sep 2024 19:46:15 +0200 Subject: [PATCH] add onKoinStartup function to help start with AndroidX startup --- docs/reference/koin-android/start.md | 26 ++++++++++ docs/setup/koin.md | 31 ++++++------ examples/androidx-samples/build.gradle | 3 +- .../koin/sample/sandbox/MainApplication.kt | 32 +++++++++---- .../org/koin/sample/sandbox/di/AppModule.kt | 19 ++++---- .../koin/sample/sandbox/main/MainActivity.kt | 11 +++++ .../src/test/java/CheckModulesTest.kt | 5 +- .../koin-androidx-startup/build.gradle.kts | 48 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 15 ++++++ .../koin/androix/startup/KoinInitializer.kt | 41 ++++++++++++++++ .../org/koin/androix/startup/KoinStartup.kt | 39 +++++++++++++++ projects/bom/koin-bom/build.gradle.kts | 1 + projects/gradle/libs.versions.toml | 11 +++-- projects/settings.gradle.kts | 1 + 14 files changed, 240 insertions(+), 43 deletions(-) create mode 100644 projects/android/koin-androidx-startup/build.gradle.kts create mode 100644 projects/android/koin-androidx-startup/src/main/AndroidManifest.xml create mode 100644 projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinInitializer.kt create mode 100644 projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinStartup.kt diff --git a/docs/reference/koin-android/start.md b/docs/reference/koin-android/start.md index f495b08f6..4946b9558 100644 --- a/docs/reference/koin-android/start.md +++ b/docs/reference/koin-android/start.md @@ -72,3 +72,29 @@ startKoin { } ``` +## Start Koin with Androidx Startup (4.0) + +By using Gradle packge `koin-androidx-startup`, we can use `onKoinStartup` function durectly in `init` block of your Application class: + +```kotlin +class MainApplication : Application() { + + init { + // Use AndroidX Startup for Koin + onKoinStartup { + androidContext(this@MainApplication) + modules(allModules) + } + } + + override fun onCreate() { + super.onCreate() + } +} +``` + +This replaces the `startKoin` function that is usally used in `onCreate`. + +:::info +Gain over from `onKoinStartup` to regular `startKoin` can go over 30% of time gained, for startup time. +::: diff --git a/docs/setup/koin.md b/docs/setup/koin.md index 85cbe572c..dcf4dec83 100644 --- a/docs/setup/koin.md +++ b/docs/setup/koin.md @@ -23,6 +23,7 @@ Here are the currently available versions: | koin-android-compat | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-android-compat)](https://mvnrepository.com/artifact/io.insert-koin/koin-android-compat) | | koin-androidx-navigation | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-navigation) | | koin-androidx-workmanager | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-workmanager)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-workmanager) | +| koin-androidx-startup | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-androidx-startup)](https://mvnrepository.com/artifact/io.insert-koin/koin-androidx-startup) | | koin-compose | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-compose)](https://mvnrepository.com/artifact/io.insert-koin/koin-compose) | | koin-compose-viewmodel | [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-compose-viewmodel)](https://mvnrepository.com/artifact/io.insert-koin/koin-compose-viewmodel) | | koin-compose-viewmodel-navigation| [![Maven Central](https://img.shields.io/maven-central/v/io.insert-koin/koin-compose-viewmodel-navigation)](https://mvnrepository.com/artifact/io.insert-koin/koin-compose-viewmodel-navigation) | @@ -128,6 +129,8 @@ dependencies { implementation("io.insert-koin:koin-androidx-workmanager:$koin_android_version") // Navigation Graph implementation("io.insert-koin:koin-androidx-navigation:$koin_android_version") + // App Startup + implementation("io.insert-koin:koin-androidx-startup:$koin_android_version") } ``` @@ -135,33 +138,27 @@ dependencies { From now you can continue on Koin Tutorials to learn about using Koin: [Android App Tutorial](/docs/quickstart/android-viewmodel) ::: -### **Android Jetpack Compose** +### **Jetpack Compose or Compose Multiplatform** + +Add `koin-compose` dependency to your multiplatform application, for use Koin & Compose API: ```groovy dependencies { - implementation("io.insert-koin:koin-androidx-compose:$koin_android_compose_version") + implementation("io.insert-koin:koin-compose:$koin_version") + implementation("io.insert-koin:koin-compose-viewmodel:$koin_version") + implementation("io.insert-koin:koin-compose-viewmodel-navigation:$koin_version") } ``` -You are now ready to start Koin in your `Application` class: +If you are using pure Android Jetpack Compose, you can go with -```kotlin -class MainApplication : Application() { - override fun onCreate() { - super.onCreate() - - startKoin { - modules(appModule) - } - } +```groovy +dependencies { + implementation("io.insert-koin:koin-androidx-compose:$koin_version") + implementation("io.insert-koin:koin-androidx-compose-navigation:$koin_version") } ``` -:::info -From now you can continue on Koin Tutorials to learn about using Koin: [Android Compose App Tutorial](/docs/quickstart/android-compose) -::: - - ### **Kotlin Multiplatform** Add `koin-core` dependency to your multiplatform application, for shared Kotlin part: diff --git a/examples/androidx-samples/build.gradle b/examples/androidx-samples/build.gradle index e62ce8c5d..4a3936d6c 100644 --- a/examples/androidx-samples/build.gradle +++ b/examples/androidx-samples/build.gradle @@ -57,7 +57,7 @@ android { dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "androidx.work:work-runtime-ktx:2.9.0" + implementation "androidx.work:work-runtime-ktx:2.9.1" implementation 'com.squareup.leakcanary:leakcanary-android:2.7' testImplementation 'androidx.arch.core:core-testing:2.2.0' testImplementation "junit:junit:4.13.2" @@ -68,6 +68,7 @@ dependencies { implementation "io.insert-koin:koin-core-coroutines" implementation "io.insert-koin:koin-androidx-workmanager" implementation "io.insert-koin:koin-androidx-navigation" + implementation "io.insert-koin:koin-androidx-startup" testImplementation "io.insert-koin:koin-test-junit4" testImplementation "io.insert-koin:koin-android-test" } diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/MainApplication.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/MainApplication.kt index 6afaa9b58..1ff700e9b 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/MainApplication.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/MainApplication.kt @@ -9,27 +9,43 @@ import org.koin.android.ext.koin.androidFileProperties import org.koin.android.ext.koin.androidLogger import org.koin.androidx.fragment.koin.fragmentFactory import org.koin.androidx.workmanager.koin.workManagerFactory +import org.koin.androix.startup.KoinStartup.onKoinStartup import org.koin.core.context.startKoin -import org.koin.core.lazyModules import org.koin.core.logger.Level import org.koin.sample.sandbox.di.allModules -class MainApplication : Application() { - override fun onCreate() { - super.onCreate() - startKoin { +class MainApplication : Application() { + + init { + onKoinStartup { androidLogger(Level.DEBUG) androidContext(this@MainApplication) androidFileProperties() fragmentFactory() workManagerFactory() -// modules(allModules) - // Lazy Modules - lazyModules(allModules) + modules(allModules) } + } + + companion object { + var startTime: Long = 0 + } + + override fun onCreate() { + super.onCreate() + startTime = System.currentTimeMillis() +// startKoin { +// androidLogger(Level.DEBUG) +// androidContext(this@MainApplication) +// androidFileProperties() +// fragmentFactory() +// workManagerFactory() +// +// modules(allModules) +// } //TODO Load/Unload Koin modules scenario cases cancelPendingWorkManager(this) diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt index ab5c28138..f9e7e91ea 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/di/AppModule.kt @@ -9,6 +9,7 @@ import org.koin.core.module.dsl.* import org.koin.core.module.includes import org.koin.core.qualifier.named import org.koin.dsl.lazyModule +import org.koin.dsl.module import org.koin.sample.sandbox.components.Counter import org.koin.sample.sandbox.components.SCOPE_ID import org.koin.sample.sandbox.components.SCOPE_SESSION @@ -32,7 +33,7 @@ import org.koin.sample.sandbox.scope.ScopedFragment import org.koin.sample.sandbox.workmanager.SimpleWorker import org.koin.sample.sandbox.workmanager.SimpleWorkerService -val appModule = lazyModule { +val appModule = module { singleOf(::SimpleServiceImpl) { bind() } singleOf(::DumbServiceImpl) { @@ -42,7 +43,7 @@ val appModule = lazyModule { factory { RandomId() } } -val mvpModule = lazyModule { +val mvpModule = module { //factory { (id: String) -> FactoryPresenter(id, get()) } factoryOf(::FactoryPresenter) @@ -52,7 +53,7 @@ val mvpModule = lazyModule { } } -val mvvmModule = lazyModule { +val mvvmModule = module { viewModelOf(::SimpleViewModel)// { (id: String) -> SimpleViewModel(id, get()) } viewModelOf(::SimpleViewModel) { named("vm1") } //{ (id: String) -> SimpleViewModel(id, get()) } @@ -92,7 +93,7 @@ val mvvmModule = lazyModule { } } -val scopeModule = lazyModule { +val scopeModule = module { scope(named(SCOPE_ID)) { scopedOf(::Session) { named(SCOPE_SESSION) @@ -105,7 +106,7 @@ val scopeModule = lazyModule { } } -val scopeModuleActivityA = lazyModule { +val scopeModuleActivityA = module { scope { fragmentOf(::ScopedFragment) scopedOf(::Session) @@ -116,19 +117,19 @@ val scopeModuleActivityA = lazyModule { } } -val workerServiceModule = lazyModule { +val workerServiceModule = module { singleOf(::SimpleWorkerService) } -val workerScopedModule = lazyModule { +val workerScopedModule = module { workerOf(::SimpleWorker)// { SimpleWorker(get(), androidContext(), it.get()) } } -val navModule = lazyModule { +val navModule = module { viewModelOf(::NavViewModel) viewModelOf(::NavViewModel2) } -val allModules = lazyModule { +val allModules = module { includes(appModule, mvpModule, mvvmModule , scopeModule , workerServiceModule , workerScopedModule , navModule , scopeModuleActivityA) } \ No newline at end of file diff --git a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/main/MainActivity.kt b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/main/MainActivity.kt index 2eb646149..9767495c0 100644 --- a/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/main/MainActivity.kt +++ b/examples/androidx-samples/src/main/java/org/koin/sample/sandbox/main/MainActivity.kt @@ -1,12 +1,14 @@ package org.koin.sample.sandbox.main import android.os.Bundle +import android.util.Log import android.widget.Button import androidx.appcompat.app.AppCompatActivity import org.koin.android.ext.android.get import org.koin.android.ext.android.getKoin import org.koin.android.ext.android.inject import org.koin.core.qualifier.named +import org.koin.sample.sandbox.MainApplication import org.koin.sample.sandbox.R import org.koin.sample.sandbox.components.APP_TITLE import org.koin.sample.sandbox.components.main.* @@ -46,4 +48,13 @@ class MainActivity : AppCompatActivity() { navigateTo(isRoot = true) } } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + val endTime = System.currentTimeMillis() + val startupTime = endTime - MainApplication.startTime + Log.i("[MEASURE]","App startup time - $startupTime ms") + } + } } \ No newline at end of file diff --git a/examples/androidx-samples/src/test/java/CheckModulesTest.kt b/examples/androidx-samples/src/test/java/CheckModulesTest.kt index fc3cad0ed..96268aa46 100644 --- a/examples/androidx-samples/src/test/java/CheckModulesTest.kt +++ b/examples/androidx-samples/src/test/java/CheckModulesTest.kt @@ -1,14 +1,11 @@ import org.junit.Test import org.koin.android.test.verify.androidVerify -import org.koin.android.test.verify.verify import org.koin.sample.sandbox.di.allModules -import org.koin.sample.sandbox.di.appModule -import org.koin.sample.sandbox.di.mvpModule class CheckModulesTest { @Test fun `Verify Configuration`() { - allModules.value.androidVerify() + allModules.androidVerify() } } \ No newline at end of file diff --git a/projects/android/koin-androidx-startup/build.gradle.kts b/projects/android/koin-androidx-startup/build.gradle.kts new file mode 100644 index 000000000..6d9ed67a9 --- /dev/null +++ b/projects/android/koin-androidx-startup/build.gradle.kts @@ -0,0 +1,48 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinAndroid) +} + +val androidCompileSDK : String by project +val androidMinSDK : String by project + +android { + namespace = "org.koin.androidx.startup" + compileSdk = androidCompileSDK.toInt() + defaultConfig { + minSdk = androidMinSDK.toInt() + } + buildFeatures { + buildConfig = false + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +tasks.withType().all { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + } +} + +dependencies { + api(project(":android:koin-android")) + api(libs.androidx.workmanager) + + // Test + testImplementation(libs.test.junit) + testImplementation(libs.test.mockito) +} + +// android sources +val sourcesJar: TaskProvider by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(android.sourceSets.map { it.java.srcDirs }) +} + +apply(from = file("../../gradle/publish-android.gradle.kts")) diff --git a/projects/android/koin-androidx-startup/src/main/AndroidManifest.xml b/projects/android/koin-androidx-startup/src/main/AndroidManifest.xml new file mode 100644 index 000000000..55c1be01d --- /dev/null +++ b/projects/android/koin-androidx-startup/src/main/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinInitializer.kt b/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinInitializer.kt new file mode 100644 index 000000000..309eccedc --- /dev/null +++ b/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinInitializer.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.koin.androix.startup + +import android.content.Context +import androidx.startup.Initializer +import org.koin.core.Koin +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.core.context.startKoin + +/** + * KoinInitializer handle Initializer for Koin startup process + * + * @author Arnaud Giuliani + */ +class KoinInitializer : Initializer { + + @OptIn(KoinExperimentalAPI::class) + override fun create(context: Context): Koin { + return KoinStartup.koinAppDeclaration?.let { + startKoin(it).koin + } ?: error("KoinInitializer can't start Koin configuration. Please use KoinStartup.onKoinStartup() function to register your Koin application.") + } + + override fun dependencies(): List>> { + return emptyList() + } +} \ No newline at end of file diff --git a/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinStartup.kt b/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinStartup.kt new file mode 100644 index 000000000..cd2279629 --- /dev/null +++ b/projects/android/koin-androidx-startup/src/main/java/org/koin/androix/startup/KoinStartup.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.koin.androix.startup + +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.core.module.KoinApplicationDslMarker +import org.koin.dsl.KoinAppDeclaration + +/** + * KoinStartup holds KoinAppDeclaration for AndroidX Startup with KoinInitializer + * + * @author Arnaud Giuliani + */ +@KoinExperimentalAPI +object KoinStartup { + internal var koinAppDeclaration : KoinAppDeclaration? = null + + /** + * startKoin with AndroidX startup Initializer + * @see startKoin function + */ + @KoinApplicationDslMarker + fun onKoinStartup(koinAppDeclaration : KoinAppDeclaration){ + this.koinAppDeclaration = koinAppDeclaration + } +} \ No newline at end of file diff --git a/projects/bom/koin-bom/build.gradle.kts b/projects/bom/koin-bom/build.gradle.kts index d2854aa6b..70a4c4211 100644 --- a/projects/bom/koin-bom/build.gradle.kts +++ b/projects/bom/koin-bom/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { api(project(":android:koin-android-test")) api(project(":android:koin-androidx-navigation")) api(project(":android:koin-androidx-workmanager")) + api(project(":android:koin-androidx-startup")) api(project(":compose:koin-compose")) api(project(":compose:koin-compose-viewmodel")) diff --git a/projects/gradle/libs.versions.toml b/projects/gradle/libs.versions.toml index d2cf8823b..db8b777bb 100644 --- a/projects/gradle/libs.versions.toml +++ b/projects/gradle/libs.versions.toml @@ -13,11 +13,12 @@ uuid = "0.8.4" # Android agp = "7.4.2" android-appcompat = "1.7.0" -android-activity = "1.9.1" -android-fragment = "1.8.2" -androidx-lifecycle = "2.8.4" +android-activity = "1.9.2" +android-fragment = "1.8.3" +androidx-lifecycle = "2.8.5" androidx-workmanager = "2.9.1" -androidx-navigation = "2.7.7" +androidx-navigation = "2.8.0" +androidx-startup= "1.1.1" # Compose composeJetpackRuntime = "1.6.8" @@ -58,6 +59,8 @@ androidx-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", ve androidx-commonJava8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" } androidx-navigation = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" } androidx-workmanager = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-workmanager" } +androidx-startup = {module ="androidx.startup:startup-runtime", version.ref = "androidx-startup" } + # Ktor ktor-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } diff --git a/projects/settings.gradle.kts b/projects/settings.gradle.kts index c6c4ca139..7c94716e7 100644 --- a/projects/settings.gradle.kts +++ b/projects/settings.gradle.kts @@ -37,6 +37,7 @@ include( ":android:koin-androidx-navigation", ":android:koin-androidx-workmanager", ":android:koin-android-test", + ":android:koin-androidx-startup", // Compose ":compose:koin-compose", ":compose:koin-compose-viewmodel",