Skip to content
Open
4 changes: 2 additions & 2 deletions examples/gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ ext {
// Kotlin
kotlin_version = '2.1.20'
// Koin Versions
koin_version = '4.1.1'
koin_version = '4.1.2-Beta1'
koin_android_version = koin_version
koin_compose_version = koin_version

coroutines_version = "1.9.0"
ktor_version = "3.1.3"//"3.2.0-eap-1310"
ktor_version = "3.2.2"
jb_compose_version = "1.8.0"

// Test
Expand Down
32 changes: 32 additions & 0 deletions examples/ktor-di-sample/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apply plugin: 'kotlin'
apply plugin: 'application'

archivesBaseName = 'ktor-di-sample'
mainClassName = 'org.koin.sample.ApplicationKt'

dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-server-call-logging:$ktor_version"
implementation "io.ktor:ktor-server-di:$ktor_version"

// Use local Koin project dependencies
implementation platform("io.insert-koin:koin-bom:$koin_version")
implementation "io.insert-koin:koin-ktor"
implementation "io.insert-koin:koin-logger-slf4j"
implementation("org.slf4j:slf4j-api:2.0.16")
implementation "ch.qos.logback:logback-classic:1.5.16"

testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation "io.ktor:ktor-server-test-host:$ktor_version"
testImplementation "io.insert-koin:koin-test"
testImplementation "io.insert-koin:koin-test-junit4"
}

repositories {
mavenLocal()
mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlinx" }
maven { url "https://dl.bintray.com/kotlin/ktor" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.koin.sample.ktor.di

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.di.dependencies
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.core.logger.Level
import org.koin.dsl.module
import org.koin.ktor.ext.inject
import org.koin.ktor.plugin.Koin

fun main() {
embeddedServer(Netty, port = 8080) {
mainModule()
}.start(wait = true)
}

fun Application.mainModule() {
install(CallLogging)

install(Koin) {
printLogger(Level.DEBUG)

bridge {
ktorToKoin()
koinToKtor()
}

modules(module {
single<HelloKoinService> { HelloKoinServiceImpl() }
})
}

dependencies {
provide<KtorSpecificService> { KtorSpecificServiceImpl() }
}

routing {
get("/koin") {
val helloKoinService: HelloKoinService by inject() // From koin
call.respondText(helloKoinService.sayHello())
}

get("/ktor-di") {
val ktorService: KtorSpecificService by dependencies // From Ktor DI
call.respondText(ktorService.process())
}

get("/mixed-ktor-di") {
val helloKoinService: HelloKoinService by dependencies // From Koin via Ktor DI
val ktorService: KtorSpecificService by dependencies // From Ktor DI

call.respondText("${helloKoinService.sayHello()} - ${ktorService.process()}")
}

get("/mixed-koin") {
val helloKoinService: HelloKoinService by inject() // From Koin
val ktorService: KtorSpecificService by inject() // From Ktor Di via Koin

call.respondText("${helloKoinService.sayHello()} - ${ktorService.process()}")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.koin.sample.ktor.di

interface HelloKoinService {
fun sayHello(): String
}

class HelloKoinServiceImpl : HelloKoinService {
override fun sayHello(): String = "Hello from Koin!"
}

interface KtorSpecificService {
fun process(): String
}

class KtorSpecificServiceImpl : KtorSpecificService {
override fun process(): String = "Processed by Ktor DI"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.koin.sample.ktor.di

import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.testApplication
import org.junit.Test
import org.koin.core.annotation.KoinInternalApi
import org.koin.core.logger.Level
import org.koin.ktor.ext.getKoin
import kotlin.test.assertEquals

class ApplicationTest {

@Test
fun `test koin endpoint`() = testApplication {
application {
mainModule()
}

@OptIn(KoinInternalApi::class)

val response = client.get("/koin")

assertEquals(HttpStatusCode.Companion.OK, response.status)
assertEquals("Hello from Koin!", response.bodyAsText())
}

@Test
fun `test ktor-di endpoint`() = testApplication {
application {
mainModule()
}

val response = client.get("/ktor-di")

assertEquals(HttpStatusCode.Companion.OK, response.status)
assertEquals("Processed by Ktor DI", response.bodyAsText())
}

@Test
fun `test mixed-ktor-di endpoint`() = testApplication {
application {
mainModule()
}

val response = client.get("/mixed-ktor-di")

assertEquals(HttpStatusCode.Companion.OK, response.status)
assertEquals("Hello from Koin! - Processed by Ktor DI", response.bodyAsText())
}

@Test
fun `test mixed-koin endpoint`() = testApplication {
application {
mainModule()
}

val response = client.get("/mixed-koin")

assertEquals(HttpStatusCode.Companion.OK, response.status)
assertEquals("Hello from Koin! - Processed by Ktor DI", response.bodyAsText())
}

}
1 change: 1 addition & 0 deletions examples/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include 'jvm-perfs'
include 'android-perfs'
// ktor
include 'hello-ktor'
include 'ktor-di-sample'

// Compose
include 'sample-android-compose'
Expand Down
47 changes: 47 additions & 0 deletions projects/android/koin-dagger-bridge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.android.dagger"
compileSdk = androidCompileSDK.toInt()
defaultConfig {
minSdk = androidMinSDK.toInt()
}
buildFeatures {
buildConfig = false
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
publishing {
singleVariant("release") {}
}
}

tasks.withType<KotlinCompile>().all {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}

dependencies {
api(project(":android:koin-android"))
implementation(libs.dagger.core)
}

// android sources
val sourcesJar: TaskProvider<Jar> by tasks.registering(Jar::class) {
archiveClassifier.set("sources")
from(android.sourceSets.map { it.java.srcDirs })
}

apply(from = file("../../gradle/publish-android.gradle.kts"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2025 The Android Open Source Project
*
* 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
*
* https://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.android.dagger

import dagger.hilt.EntryPoints
import org.koin.android.ext.koin.androidContext
import org.koin.core.scope.Scope
import kotlin.jvm.java
import kotlin.reflect.KClass

/**
* Retrieve given T Module Entry Point from Dagger, to make it accessible to get a Dagger definition in Koin
*
* As Dagger Component example, to be injected in Koin :
*
* @InstallIn(SingletonComponent::class)
* @EntryPoint
* interface DaggerBridge {
* fun getUserDataRepository(): UserDataRepository
* }
*
* Bridge Example in Koin Annotations:
*
* @Module
* class AppModule {
*
* @Single
* fun bridgeUserDataRepository(scope : Scope) = scope.dagger<DaggerBridge>().getUserDataRepository()
* }
*
* Bridge Example in Koin DSL:
*
* val appModule = module {
* single { dagger<DaggerBridge>().getUserDataRepository() }
* }
*
* @param T - Reified type
*/
inline fun <reified T> Scope.dagger() : T{
return EntryPoints.get(androidContext().applicationContext, T::class.java)
}

/**
* Retrieve given Module Entry Point from Dagger, to make it accessible to get a Dagger definition in Koin
*
* @param clazz - Target class type
* @see dagger() function doc
*/
fun <T> Scope.dagger(clazz : KClass<*>) : T{
return EntryPoints.get(androidContext().applicationContext, clazz.java) as T
}
1 change: 1 addition & 0 deletions projects/bom/koin-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
api(project(":android:koin-androidx-navigation"))
api(project(":android:koin-androidx-workmanager"))
api(project(":android:koin-androidx-startup"))
api(project(":android:koin-dagger-bridge"))

api(project(":compose:koin-compose"))
api(project(":compose:koin-compose-viewmodel"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import kotlin.time.measureTime
*/
@OptIn(KoinInternalApi::class)
@KoinApplicationDslMarker
class KoinApplication private constructor() {
open class KoinApplication protected constructor() {

val koin = Koin()
private var allowOverride = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ fun flatten(modules: List<Module>): Set<Module> {
}

// Add all the included modules to the stack if they haven't been visited yet.
for (module in current.includedModules) {
for (module in current.includedModules.asReversed()) {
if (module !in flatten) {
stack += module
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class CoreResolver(
?: resolveFromStackedParameters(scope,instanceContext)
?: resolveFromScopeSource(scope,instanceContext)
?: resolveFromScopeArchetype(scope,instanceContext)
?: if (lookupParent) resolveFromParentScopes(scope,instanceContext) else null
?: (if (lookupParent) resolveFromParentScopes(scope,instanceContext) else null)
?: resolveInExtensions(scope,instanceContext)
}

Expand Down
Loading
Loading