Application Setup
Set up an IDK application: define the application graph, initialize it at startup, create user contexts and sessions, and access services.
Defining Your Application Graph
Every application that uses the IDK defines its own application graph. This is an abstract class annotated with @DependencyGraph(AppScope::class) that extends AbstractAppGraph. At compile time, Metro merges all contributed bindings from the IDK modules on your classpath into this graph, giving you a single entry point to the full scope hierarchy.
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.DependencyGraph
import dev.zacsweers.metro.Named
import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.createGraphFactory
import com.sphereon.core.defaults.app.DefaultRootScopeProvider
import com.sphereon.di.app.AbstractAppGraph
import com.sphereon.di.app.RootScopeProvider
@DependencyGraph(AppScope::class)
abstract class MyAppGraph : AbstractAppGraph() {
@DependencyGraph.Factory
fun interface Factory {
fun create(
@Provides application: Any,
@Provides @Named("appId") appId: String,
@Provides @Named("profile") profile: String,
@Provides @Named("version") version: String,
@Provides rootScopeProvider: RootScopeProvider,
): MyAppGraph
}
companion object {
fun init(
application: Any,
appId: String,
profile: String,
version: String
): MyAppGraph {
val graph = createGraphFactory<MyAppGraph.Factory>().create(
application = application,
appId = appId,
profile = profile,
version = version,
rootScopeProvider = DefaultRootScopeProvider()
)
graph.initRootScopeProvider()
return graph
}
}
}
Because your graph class has @DependencyGraph(AppScope::class), Metro collects every @ContributesTo(AppScope::class) and @ContributesBinding(AppScope::class) declaration from the IDK libraries (and any of your own modules) and wires them into the generated implementation. The child scopes (UserScope and SessionScope) are created automatically as you traverse the scope hierarchy.
See Extending the DI Graph for details on Metro annotations and how to contribute your own services.
Initializing at Startup
Create the application graph once when your application starts. It lives for the entire application lifetime.
- Android/kotlin
- iOS/Swift
class MyApplication : Application() {
lateinit var appGraph: MyAppGraph
private set
override fun onCreate() {
super.onCreate()
appGraph = MyAppGraph.init(
application = this,
appId = "com.example.myapp",
profile = BuildConfig.BUILD_TYPE, // "debug" or "release"
version = BuildConfig.VERSION_NAME
)
}
}
Register your Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
...>
</application>
import SphereonIDK
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var appGraph: MyAppGraph!
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
appGraph = MyAppGraph.companion.doInit(
application: application,
appId: Bundle.main.bundleIdentifier ?? "com.example.myapp",
profile: "release",
version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
)
return true
}
}
Initialization Parameters
| Parameter | What it does |
|---|---|
application | Platform application instance (Android Context or iOS UIApplication) |
appId | Used in configuration property resolution and logging |
profile | Selects configuration values (e.g., "debug", "production") |
version | Included in logs and diagnostics |
Setting Up User Context
After creating the application graph, establish a user context. The user context represents a tenant and principal, enabling multi-tenant isolation.
Multi-Tenant Setup
For applications that serve multiple organizations or users:
- Android/kotlin
- iOS/Swift
// Get the user context manager from the app graph
val userContextManager = appGraph.userContextManager
// Create a user context for a specific tenant and user
val userContext = userContextManager.createOrGetFromInputs(
tenantInput = DefaultTenantInputString(tenant = "acme-corp"),
principalInput = DefaultPrincipalInputString(principal = "alice@acme-corp.com"),
makeActive = true
)
// Get the user context manager from the app graph
let userContextManager = appGraph.userContextManager
// Create a user context for a specific tenant and user
let userContext = userContextManager.createOrGetFromInputs(
tenantInput: DefaultTenantInputString(tenant: "acme-corp"),
principalInput: DefaultPrincipalInputString(principal: "alice@acme-corp.com"),
makeActive: true
)
Single-Tenant Setup
For applications that don't need multi-tenancy, use the anonymous context:
- Android/kotlin
- iOS/Swift
val userContext = appGraph.userContextManager.getAnonymous(makeActive = true)
let userContext = appGraph.userContextManager.getAnonymous(makeActive: true)
The anonymous context provides a default tenant and principal, simplifying the code while maintaining the same scope structure.
Creating Sessions
Sessions are where most work happens. A session represents an active working context with access to services.
- Android/kotlin
- iOS/Swift
// Create a session with a unique identifier
val sessionContextManager = userContext.sessionContextManager
val session = sessionContextManager.createOrGetFromId(
sessionId = "main-session",
makeActive = true
)
// Access the session graph for services
val sessionGraph = session.graph
// Create a session with a unique identifier
let sessionContextManager = userContext.sessionContextManager
let session = sessionContextManager.createOrGetFromId(
sessionId: "main-session",
makeActive: true
)
// Access the session graph for services
let sessionGraph = session.graph
Session Lifecycle Strategies
Different applications have different session lifecycle needs:
For mobile wallet applications, you might create a long-lived session at user login and destroy it at logout:
// On login
fun onUserLogin(userId: String) {
val userContext = appGraph.userContextManager.createOrGetFromInputs(
tenantInput = DefaultTenantInputString(tenant = "default"),
principalInput = DefaultPrincipalInputString(principal = userId),
makeActive = true
)
session = userContext.sessionContextManager.createOrGetFromId(
sessionId = "user-session",
makeActive = true
)
}
// On logout
fun onUserLogout() {
userContext.sessionContextManager.destroyById(sessionId = "user-session")
session = null
}
For credential presentation flows, you might create a short-lived session for each presentation:
suspend fun presentCredential() {
val session = userContext.sessionContextManager.createOrGetFromId(
sessionId = UUID.randomUUID().toString(),
makeActive = true
)
try {
val engagementManager = session.graph.mdocEngagementManager
// Perform presentation...
} finally {
userContext.sessionContextManager.destroyById(sessionId = session.sessionId)
}
}
Accessing Services
If you contribute your own classes to the DI graph, services are injected as constructor parameters automatically. You can also grab them directly from the session graph:
- Android/kotlin
- iOS/Swift
val sessionGraph = session.graph
val keyManager = sessionGraph.keyManagerService
val engagementManager = sessionGraph.mdocEngagementManager
val oauth2Client = sessionGraph.oauth2Client
val configProvider = sessionGraph.configProvider
let sessionGraph = session.graph
let keyManager = sessionGraph.keyManagerService
let engagementManager = sessionGraph.mdocEngagementManager
let oauth2Client = sessionGraph.oauth2Client
let configProvider = sessionGraph.configProvider
Complete Setup Example
Here's a complete example combining all the setup steps:
- Android/kotlin
- iOS/Swift
class MyApplication : Application() {
lateinit var appGraph: MyAppGraph
private set
override fun onCreate() {
super.onCreate()
appGraph = MyAppGraph.init(
application = this,
appId = "com.example.wallet",
profile = BuildConfig.BUILD_TYPE,
version = BuildConfig.VERSION_NAME
)
}
}
class MainActivity : ComponentActivity() {
private lateinit var session: SessionInstance
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get the IDK app graph
val appGraph = (application as MyApplication).appGraph
// Create user context (anonymous for this example)
val userContext = appGraph.userContextManager.getAnonymous(makeActive = true)
// Create a session
session = userContext.sessionContextManager.createOrGetFromId(
sessionId = "main-session",
makeActive = true
)
// Now you can use IDK services
val keyManager = session.graph.keyManagerService
val engagementManager = session.graph.mdocEngagementManager
// Continue with your app logic...
}
override fun onDestroy() {
super.onDestroy()
// Clean up session if needed
session.sessionContextManager.destroyById(sessionId = "main-session")
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var appGraph: MyAppGraph!
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
appGraph = MyAppGraph.companion.doInit(
application: application,
appId: Bundle.main.bundleIdentifier ?? "com.example.wallet",
profile: "release",
version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
)
return true
}
}
class WalletViewController: UIViewController {
private var session: SessionInstance!
override func viewDidLoad() {
super.viewDidLoad()
// Get the IDK app graph
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let appGraph = appDelegate.appGraph
// Create user context (anonymous for this example)
let userContext = appGraph.userContextManager.getAnonymous(makeActive: true)
// Create a session
session = userContext.sessionContextManager.createOrGetFromId(
sessionId: "main-session",
makeActive: true
)
// Now you can use IDK services
let keyManager = session.graph.keyManagerService
let engagementManager = session.graph.mdocEngagementManager
// Continue with your app logic...
}
deinit {
// Clean up session if needed
session.sessionContextManager.destroyById(sessionId: "main-session")
}
}
Next Steps
With your application set up, you can now:
- Configure multi-tenancy if your application serves multiple organizations
- Set up configuration properties for customizing behavior
- Start using IDK services like key management or mDoc presentation