Amazon LastMile DI Integration
The IDK uses kotlin-inject for dependency injection, which is compatible with Amazon's LastMile DI approach. This guide covers how to integrate the IDK's DI components into your own application's dependency injection graph.
Overview
The IDK provides pre-built components at each scope level that you can extend or use directly. These components use Kotlin Symbol Processing (KSP) to generate the actual dependency injection code at compile time.
If you're using kotlin-inject in your own application, you can integrate with the IDK by extending its components. If you're not using kotlin-inject, you can still use the IDK's pre-built components directly.
Using Pre-built Components
The simplest approach is to use the IDK's pre-built components directly:
- Android/kotlin
- iOS/Swift
// The IdkAppComponent provides everything you need
val appComponent = IdkAppComponent.init(
application = applicationContext,
appId = "my-app",
profile = "production",
version = "1.0.0"
)
// Access services through the standard scope hierarchy
val userContext = appComponent.userContextManager.getAnonymous(makeActive = true)
val session = userContext.sessionContextManager.createOrGetFromId("session-1", makeActive = true)
val keyManager = session.component.keyManagerService
// The IdkAppComponent provides everything you need
let appComponent = IdkAppComponent.companion.doInit(
application: UIApplication.shared,
appId: "my-app",
profile: "production",
version: "1.0.0"
)
// Access services through the standard scope hierarchy
let userContext = appComponent.userContextManager.getAnonymous(makeActive: true)
let session = userContext.sessionContextManager.createOrGetFromId(sessionId: "session-1", makeActive: true)
let keyManager = session.component.keyManagerService
Extending IDK Components
If your application uses kotlin-inject and you want to add your own services to the IDK's dependency graph, you can extend the IDK components.
Setting Up kotlin-inject
First, add kotlin-inject and KSP to your project:
// build.gradle.kts
plugins {
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
}
dependencies {
implementation("me.tatarka.inject:kotlin-inject-runtime:0.6.3")
ksp("me.tatarka.inject:kotlin-inject-compiler-ksp:0.6.3")
}
Creating Custom Components
Create your own component that extends the IDK's session component:
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
import com.sphereon.idk.core.di.SessionScopeComponent
import com.sphereon.idk.core.di.SessionScope
@Component
@SessionScope
abstract class MySessionComponent(
@Component val parent: SessionScopeComponent
) : SessionScopeComponent by parent {
// Add your own services
abstract val myCustomService: MyCustomService
// Provide dependencies for your services
@Provides
fun provideMyCustomService(
keyManager: KeyManagerService,
configProvider: ConfigProvider
): MyCustomService = MyCustomServiceImpl(keyManager, configProvider)
}
Using Custom Components
Create an extension function to make accessing your custom component easier:
fun SessionContextInstance.myComponent(): MySessionComponent {
return MySessionComponent::class.create(this.component as SessionScopeComponent)
}
// Usage
val session = userContext.sessionContextManager.createOrGetFromId("session-1", makeActive = true)
val myService = session.myComponent().myCustomService
Component Hierarchy
The IDK provides components at each scope level:
| Scope | Interface | Pre-built Implementation |
|---|---|---|
| Application | AppScopeComponent | IdkAppComponent |
| User Context | UserContextScopeComponent | Internal |
| Session | SessionScopeComponent | Internal |
Each component exposes the services available at that scope level. When you extend a component, you inherit all the IDK services and can add your own.
Scope Annotations
The IDK defines scope annotations that kotlin-inject uses to manage instance lifetimes:
@Scope
annotation class AppScope
@Scope
annotation class UserContextScope
@Scope
annotation class SessionScope
When you annotate a component or binding with one of these annotations, kotlin-inject ensures that only one instance exists within that scope.
Best Practices
When integrating with the IDK's DI system, consider these practices:
Keep your custom components focused. Rather than adding many services to a single component, consider creating multiple focused components that each extend the appropriate IDK component.
Use constructor injection for your services. This makes dependencies explicit and testing easier:
class MyCustomServiceImpl(
private val keyManager: KeyManagerService,
private val configProvider: ConfigProvider
) : MyCustomService {
// Implementation
}
Leverage the IDK's scope hierarchy. If your service needs session-specific state, put it in the session scope. If it's tenant-specific, use the user context scope.
Avoid circular dependencies. If service A depends on service B and service B depends on service A, restructure your services to break the cycle.
Testing with DI
Kotlin-inject makes testing straightforward. You can create test components that provide mock implementations:
@Component
@SessionScope
abstract class TestSessionComponent : SessionScopeComponent {
@Provides
fun provideKeyManager(): KeyManagerService = mockk()
@Provides
fun provideConfigProvider(): ConfigProvider = mockk()
}
class MyServiceTest {
private val component = TestSessionComponent::class.create()
@Test
fun testService() {
val myService = MyCustomServiceImpl(
component.keyManager,
component.configProvider
)
// Test the service
}
}
Migration from Other DI Frameworks
If you're migrating from another DI framework like Dagger or Koin, the concepts map fairly directly:
| Concept | Dagger | Koin | kotlin-inject |
|---|---|---|---|
| Module | @Module | module { } | @Component abstract class |
| Binding | @Provides | single { } | @Provides |
| Scope | @Singleton | single | @Scope annotation |
| Injection | @Inject | inject() | Constructor parameters |
The main difference is that kotlin-inject generates code at compile time using KSP, making it fully compatible with Kotlin Multiplatform.