Skip to main content
Version: v0.13

Scopes and Lifecycle

The IDK uses a hierarchical scope system to manage component lifecycles, enable multi-tenant isolation, and provide proper dependency injection. Understanding this scope model is essential for correctly integrating the IDK into your application.

Scope Hierarchy

The IDK defines four scope levels, each building upon the previous:

DI Scope Hierarchy

Each scope level has an associated component that provides access to services available at that level. Services are instantiated within their appropriate scope and share the lifecycle of that scope.

Application Scope

The application scope represents the top-level singleton scope for your entire application. There is typically one application scope instance per running application or service. The application component is created once at startup and lives for the duration of the application.

Services at this level include global configuration, application-wide logging, and the context manager that creates lower-level scopes.

User Context Scope

The user context scope represents a specific tenant and principal combination. A principal is either a user (natural person) or a system account. This scope enables multi-tenant applications to isolate data and configuration between different users or organizations.

For mobile applications that don't require multi-tenancy, you can use the anonymous context which provides a default tenant and principal. The user context scope persists across multiple sessions and holds tenant-specific configuration values.

Session Scope

The session scope represents an active working session. What constitutes a session is defined by your application. In a mobile app, a session might span from user login to logout. In a REST API, each request might be a separate session.

Most IDK services are obtained from the session scope, including the KeyManagerService for cryptographic operations, the MdocEngagementManager for credential presentation, and HTTP clients configured with the appropriate credentials.

Creating Components

Components are created by traversing down the scope hierarchy. You start with the application component and use its managers to create user context and session instances.

// 1. Create the application component (singleton)
val appComponent = IdkAppComponent.init(
application = applicationContext,
appId = "my-app",
profile = "production",
version = "1.0.0"
)

// 2. Create a user context (for a specific tenant/principal)
val userContext = appComponent.userContextManager.createOrGetFromInputs(
tenantInput = DefaultTenantInputString(tenant = "acme-corp"),
principalInput = DefaultPrincipalInputString(principal = "user@example.com"),
makeActive = true
)

// Or use the anonymous context if multi-tenancy is not needed
// val userContext = appComponent.userContextManager.getAnonymous(makeActive = true)

// 3. Create a session within that user context
val session = userContext.sessionContextManager.createOrGetFromId(
sessionId = "session-123",
makeActive = true
)

// 4. Access services from the session component
val keyManager = session.component.keyManagerService
val engagementManager = session.component.engagementManager

Scope Lifecycle

Understanding when scopes are created and destroyed helps manage resources correctly.

Application Lifecycle

The application component should be created once when your application starts. On Android, this is typically in your Application class. On iOS, this is typically in your AppDelegate or during initial app setup.

class MyApplication : Application() {
lateinit var idkApp: IdkAppComponent
private set

override fun onCreate() {
super.onCreate()
idkApp = IdkAppComponent.init(
application = this,
appId = "my-app",
profile = BuildConfig.BUILD_TYPE,
version = BuildConfig.VERSION_NAME
)
}
}

User Context Lifecycle

User contexts can be created and retrieved by ID. If a context with the given tenant and principal already exists, it's returned rather than creating a new one. User contexts are typically long-lived and persist across sessions.

// Creating or retrieving a user context
val context = appComponent.userContextManager.createOrGetFromInputs(
tenantInput = DefaultTenantInputString(tenant = "tenant-id"),
principalInput = DefaultPrincipalInputString(principal = "user-id"),
makeActive = true
)

// Later, you can retrieve an active context
val activeContext = appComponent.userContextManager.getActive()

Session Lifecycle

Sessions are created within a user context and should be destroyed when no longer needed. Destroying a session cancels any outstanding asynchronous operations and releases resources.

// Create a session
val session = userContext.sessionContextManager.createOrGetFromId(
sessionId = "session-123",
makeActive = true
)

// Use the session...

// Destroy when done (optional but recommended)
userContext.sessionContextManager.destroy(sessionId = "session-123")

Service Access Patterns

Services are typically accessed through the session component. The component ensures that all dependencies are properly injected and scoped.

// Direct property access
val keyManager = session.component.keyManagerService

// Generic service access by type
val oauth2Client: OAuth2Client = session.getService()

// Generic service access by ID
val customService = session.getService<MyService>(MyService.SERVICE_ID)

Configuration by Scope

Configuration values can be set at different scope levels, with more specific scopes overriding less specific ones. This enables patterns like:

  • Application-wide default settings
  • Tenant-specific overrides for API endpoints
  • Principal-specific credentials
  • Session-specific temporary values

See the Configuration guide for details on how to configure values at each scope level.

Best Practices

When working with the scope system, keep these practices in mind:

The application component should be created once and stored in a location accessible throughout your app, such as the Application class on Android or a singleton on iOS.

For mobile applications without multi-tenancy requirements, use the anonymous context. This simplifies your code while still providing proper scope isolation.

Create sessions with meaningful IDs that help with debugging and logging. The session ID appears in logs and helps trace operations across the system.

Destroy sessions when they're no longer needed, especially in server applications where sessions represent individual requests. This ensures resources are released promptly.

Avoid holding references to components or services outside their scope's lifecycle. If a session is destroyed, services obtained from that session should no longer be used.