Scopes and Dependency Injection
The Kiwa eLicense Holder SDK uses a hierarchical scope system to manage component lifecycles and service instances. This architecture is inherited from the Identity Development Kit (IDK).
Scope Hierarchy
The SDK operates with three nested scopes:
| Scope | Lifecycle | Purpose |
|---|---|---|
| App | Application lifetime | Singletons, configuration, context manager |
| Context | Tenant/Principal | Multi-tenancy support, per-user configuration |
| Session | Work session | Service instances, coroutine grouping |
Initialization
Step 1: Initialize the Application Component
The app component is typically created once during application startup. It manages application-wide singletons and provides access to the context manager.
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.KiwaSdkAppComponent
// Create the application component
val appComponent = KiwaSdkAppComponent.init(
application = yourApplication, // Android Application instance
appId = "your-app-id", // Unique identifier for your app
profile = "prod", // Environment profile (prod, test, dev, etc.)
version = "1.0.0" // Your application version
)
import KiwaSdk
let application = UIApplication.shared
// Create the application component
let appComponent = KiwaSdkAppComponent.companion.doInit(
application: application, // UIApplication instance
appId: Bundle.main.bundleIdentifier ?? "your-app-id",
profile: "prod", // Environment profile
version: "1.0.0" // Your application version
)
Parameters:
| Parameter | Description |
|---|---|
application | Platform application instance (Android Application or iOS UIApplication) |
appId | Unique identifier used in configuration management |
profile | Environment profile for configuration separation (e.g., "prod", "test") |
version | Application version (reserved for future use) |
Step 2: Initialize the Context Scope
The context scope manages tenant and principal (user) information. This scope enables multi-tenancy and per-user configuration.
- Android/Kotlin
- iOS/Swift
import com.sphereon.idk.common.context.DefaultTenantInputString
import com.sphereon.idk.common.context.DefaultPrincipalInputString
// Initialize with tenant and principal
val userContextInstance = appComponent.userContextManager
.createOrGetFromInputs(
tenantInput = DefaultTenantInputString(tenant = "your-tenant-id"),
principalInput = DefaultPrincipalInputString(principal = "user@example.com")
)
val userContextComponent = userContextInstance.component
// Alternative: Anonymous context (for apps without multi-tenancy)
// val userContextInstance = appComponent.userContextManager.getAnonymous(makeActive = true)
// Initialize with tenant and principal
let userContextInstance = appComponent.userContextManager
.createOrGetFromInputs(
tenantInput: DefaultTenantInputString(tenant: "your-tenant-id"),
principalInput: DefaultPrincipalInputString(principal: "user@example.com"),
makeActive: true
)
let userContextComponent = userContextInstance.component
// Alternative: Anonymous context (for apps without multi-tenancy)
// let userContextInstance = appComponent.userContextManager.getAnonymous(makeActive: true)
For mobile apps without multi-tenancy requirements, use the anonymous context. This simplifies initialization while still satisfying the SDK's scope requirements.
Step 3: Initialize the Session Scope
The session scope groups related operations and manages service instances. What constitutes a "session" is application-defined:
- Mobile apps: From user login to logout
- REST APIs: A single request/response cycle
- Background tasks: Duration of the task
- Android/Kotlin
- iOS/Swift
// Initialize session with a unique identifier
val sessionContextManager = userContextInstance.sessionContextManager
val sessionInstance = sessionContextManager.createOrGetFromId(
sessionId = "unique-session-id",
makeActive = true
)
val sessionComponent = sessionInstance.component
// Initialize session with a unique identifier
let sessionContextManager = userContextInstance.sessionContextManager
let sessionInstance = sessionContextManager.createOrGetFromId(
sessionId: "unique-session-id",
makeActive: true
)
let sessionComponent = sessionInstance.component
When a session is destroyed, all outstanding asynchronous work within that session is cancelled. This ensures clean resource management.
Accessing Services
Once the scopes are initialized, retrieve service instances from the session:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.getKiwaServices
// Get the main KiwaServices entry point
val kiwaServices = sessionInstance.getKiwaServices()
// Alternative methods:
// val kiwaServices: KiwaServices = sessionInstance.getService()
// val kiwaServices = sessionInstance.getService<KiwaServices>(KiwaServices.SERVICE_ID)
// Access individual services
val authService = kiwaServices.auth // Wallet certificate operations
val holderService = kiwaServices.holder // License lifecycle operations
val cryptoServices = kiwaServices.crypto // Cryptographic operations
// Get the main KiwaServices entry point
let kiwaServices = sessionInstance.getKiwaServices()
// Alternative methods:
// let kiwaServices = sessionInstance.getService(id: KiwaServicesCompanion.shared.SERVICE_ID) as! KiwaServices
// Access individual services
let authService = kiwaServices.auth // Wallet certificate operations
let holderService = kiwaServices.holder // License lifecycle operations
let cryptoServices = kiwaServices.crypto // Cryptographic operations
Service Interfaces
| Service | Purpose | Key Operations |
|---|---|---|
KiwaServices | Main entry point | Access to all sub-services |
AuthService | Authentication | getWalletCertificate, renewWalletCertificate |
HolderService | License management | issue, assign, confirm, decode |
KiwaCryptoServices | Cryptography | Certificate/key storage and management |
Complete Initialization Example
Here's a complete example showing all initialization steps:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.KiwaSdkAppComponent
import com.sphereon.kiwa.elicense.sdk.getKiwaServices
import com.sphereon.idk.common.context.DefaultTenantInputString
import com.sphereon.idk.common.context.DefaultPrincipalInputString
class MyApplication : Application() {
lateinit var kiwaServices: KiwaServices
private set
override fun onCreate() {
super.onCreate()
initializeKiwaSDK()
}
private fun initializeKiwaSDK() {
// Step 1: App component
val appComponent = KiwaSdkAppComponent.init(
application = this,
appId = "my-kiwa-app",
profile = "prod",
version = BuildConfig.VERSION_NAME
)
// Step 2: Context scope (using anonymous for simplicity)
val userContextInstance = appComponent.userContextManager
.getAnonymous(makeActive = true)
// Step 3: Session scope
val sessionInstance = userContextInstance.sessionContextManager
.createOrGetFromId(sessionId = "main-session", makeActive = true)
// Step 4: Get services
kiwaServices = sessionInstance.getKiwaServices()
}
}
import KiwaSdk
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var kiwaServices: KiwaServices!
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
initializeKiwaSDK(application: application)
return true
}
private func initializeKiwaSDK(application: UIApplication) {
// Step 1: App component
let appComponent = KiwaSdkAppComponent.companion.doInit(
application: application,
appId: Bundle.main.bundleIdentifier ?? "my-kiwa-app",
profile: "prod",
version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
)
// Step 2: Context scope (using anonymous for simplicity)
let userContextInstance = appComponent.userContextManager
.getAnonymous(makeActive: true)
// Step 3: Session scope
let sessionInstance = userContextInstance.sessionContextManager
.createOrGetFromId(sessionId: "main-session", makeActive: true)
// Step 4: Get services
kiwaServices = sessionInstance.getKiwaServices()
}
}
Advanced: Custom Dependency Injection
If you're using kotlin-inject in your own application, you can inject KiwaServices directly:
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Component
import com.sphereon.kiwa.elicense.sdk.KiwaServices
@Inject
class MyLicenseManager(
private val kiwaServices: KiwaServices
) {
suspend fun fetchLicenses() {
val result = kiwaServices.holder.commands.issue.execute(/* request */)
// Handle result
}
}
See the IDK Dependency Injection documentation for advanced DI patterns.
Next Steps
With the SDK initialized, proceed to:
- Holder Functions - Implement the complete license lifecycle
- eLicense Mdocs - Work with license display and verification