Version: v0.25.0 (Latest)
Device Retrieval Methods
The device retrieval methods determine how credential data is transferred after engagement is established. The IDK supports multiple retrieval transports that can be configured on engagement instances.
Available Retrieval Methods
| Method | Description | Use Case |
|---|---|---|
| BLE Central Client | Device connects as BLE central | Most common for mobile wallets |
| BLE Peripheral Server | Device advertises as BLE peripheral | Reader-initiated flows |
| NFC | Near Field Communication | Tap-and-go scenarios |
| WiFi Aware | Direct WiFi connection | High bandwidth transfers |
| REST API | HTTP-based retrieval | Remote verification |
Accessing Retrieval Methods
The available retrieval methods are exposed through the EngagementInstance:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.graph.mdocEngagementManager
val engagement = engagementManager.qrEngagement.value
// Get available retrieval methods
val retrievalMethods: Set<DeviceRetrievalMethod> = engagement?.getRetrievalMethods() ?: emptySet()
for (method in retrievalMethods) {
when (method) {
is DeviceRetrievalMethod.Ble -> {
println("BLE available")
println(" Central client: ${method.centralClientMode}")
println(" Peripheral server: ${method.peripheralServerMode}")
}
is DeviceRetrievalMethod.Nfc -> {
println("NFC available")
}
is DeviceRetrievalMethod.Wifi -> {
println("WiFi Aware available")
}
}
}
let engagementManager = session.graph.mdocEngagementManager
let engagement = engagementManager.qrEngagement.value
// Get available retrieval methods
let retrievalMethods: Set<DeviceRetrievalMethod> = engagement?.getRetrievalMethods() ?? []
for method in retrievalMethods {
switch method {
case let ble as DeviceRetrievalMethod.Ble:
print("BLE available")
print(" Central client: \(ble.centralClientMode)")
print(" Peripheral server: \(ble.peripheralServerMode)")
case is DeviceRetrievalMethod.Nfc:
print("NFC available")
case is DeviceRetrievalMethod.Wifi:
print("WiFi Aware available")
default:
break
}
}
QR Code Engagement Flow
For QR code-based engagement, the holder displays a QR code and the verifier scans it:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.graph.mdocEngagementManager
// Observe QR engagement state
lifecycleScope.launch {
engagementManager.qrEngagement.collect { engagement ->
if (engagement != null) {
// Get the URI to encode in QR code
val qrUri = engagement.getEngagementUri()
displayQrCode(qrUri)
// Monitor engagement state
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
// Verifier connected, start transfer
val transferManager = engagement.start()
handleTransfer(transferManager)
}
is MdocEngagementState.Error -> {
showError((event.state as MdocEngagementState.Error).error)
}
}
}
}
}
}
let engagementManager = session.graph.mdocEngagementManager
// Observe QR engagement state
Task {
for await engagement in engagementManager.qrEngagement {
guard let engagement = engagement else { continue }
// Get the URI to encode in QR code
let qrUri = try await engagement.getEngagementUri()
displayQrCode(uri: qrUri)
// Monitor engagement state
for await event in engagement.events {
switch event.state {
case .connected:
// Verifier connected, start transfer
let transferManager = try await engagement.start()
try await handleTransfer(transferManager: transferManager)
case .error(let error):
showError(error: error)
default:
break
}
}
}
}
TO_APP Engagement Flow
For app-to-app engagement initiated by the verifier via deep link:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.graph.mdocEngagementManager
// Handle incoming deep link
fun handleIncomingUri(uri: String) {
lifecycleScope.launch {
// Create engagement from URI
val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true // Automatically start the engagement
)
if (result.isOk) {
val engagement = result.value
// Get transfer manager (auto-started)
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
// Transfer ready
}
}
}
} else {
showError(result.error)
}
}
}
let engagementManager = session.graph.mdocEngagementManager
// Handle incoming deep link
func handleIncomingUri(uri: String) async {
// Create engagement from URI
let result = engagementManager.toApp(
mdocUri: uri,
autoStart: true // Automatically start the engagement
)
if result.isOk {
let engagement = result.value
// Monitor engagement events
for await event in engagement.events {
switch event.state {
case .connected:
// Transfer ready
break
default:
break
}
}
} else {
showError(error: result.error)
}
}
NFC Engagement Flow
For NFC tap-based engagement:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.graph.mdocEngagementManager
// Observe NFC engagement
lifecycleScope.launch {
engagementManager.nfcEngagement.collect { engagement ->
if (engagement != null) {
// NFC tap detected, engagement created
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
// Start transfer after NFC connection
val transferManager = engagement.start()
handleTransfer(transferManager)
}
}
}
}
}
}
let engagementManager = session.graph.mdocEngagementManager
// Observe NFC engagement
Task {
for await engagement in engagementManager.nfcEngagement {
guard let engagement = engagement else { continue }
// NFC tap detected, engagement created
for await event in engagement.events {
switch event.state {
case .connected:
// Start transfer after NFC connection
let transferManager = try await engagement.start()
try await handleTransfer(transferManager: transferManager)
default:
break
}
}
}
}
Shared Parameters
The SharedParameters manage BLE UUIDs and ephemeral keys shared across engagements:
- Android/Kotlin
- iOS/Swift
val sharedParams = engagementManager.sharedParameters
// Access BLE UUIDs
val centralClientUuid = sharedParams.bleCentralClientUuid.value
val peripheralServerUuid = sharedParams.blePeripheralServerUuid.value
// Ephemeral key alias (managed by KMS)
val ephemeralKeyAlias = sharedParams.ephemeralKeyAlias.value
// Regenerate parameters for new session
sharedParams.regenerate()
// Use same UUID for both BLE modes (some verifiers require this)
sharedParams.useSameUuidForBothModes()
let sharedParams = engagementManager.sharedParameters
// Access BLE UUIDs
let centralClientUuid = sharedParams.bleCentralClientUuid.value
let peripheralServerUuid = sharedParams.blePeripheralServerUuid.value
// Ephemeral key alias (managed by KMS)
let ephemeralKeyAlias = sharedParams.ephemeralKeyAlias.value
// Regenerate parameters for new session
sharedParams.regenerate()
// Use same UUID for both BLE modes (some verifiers require this)
sharedParams.useSameUuidForBothModes()
Active Engagement Tracking
Only one engagement can be active at a time:
- Android/Kotlin
- iOS/Swift
// Track the currently active engagement
lifecycleScope.launch {
engagementManager.activeEngagement.collect { active ->
if (active != null) {
updateUI("Active engagement: ${active.data.type}")
} else {
updateUI("No active engagement")
}
}
}
// Track all engagements by type
lifecycleScope.launch {
engagementManager.engagementsByType.collect { engagements ->
for ((type, instance) in engagements) {
println("$type: ${instance.id}")
}
}
}
// Track the currently active engagement
Task {
for await active in engagementManager.activeEngagement {
if let active = active {
updateUI(message: "Active engagement: \(active.data.type)")
} else {
updateUI(message: "No active engagement")
}
}
}
// Track all engagements by type
Task {
for await engagements in engagementManager.engagementsByType {
for (type, instance) in engagements {
print("\(type): \(instance.id)")
}
}
}
Closing Engagements
Clean up engagements when done:
- Android/Kotlin
- iOS/Swift
// Close specific engagement types
engagementManager.closeQrEngagement()
engagementManager.closeNfcEngagement()
engagementManager.closeToAppEngagement()
// Close by instance
engagementManager.closeEngagementByInstance(engagement)
// Close by ID
engagementManager.closeEngagementById(engagementId)
// Close all engagements
engagementManager.closeAll()
// Close specific engagement types
engagementManager.closeQrEngagement()
engagementManager.closeNfcEngagement()
engagementManager.closeToAppEngagement()
// Close by instance
engagementManager.closeEngagementByInstance(engagement: engagement)
// Close by ID
engagementManager.closeEngagementById(id: engagementId)
// Close all engagements
engagementManager.closeAll()
Tips
- Support QR, NFC, and TO_APP engagement types for maximum compatibility.
- Use event streams to provide responsive UI feedback on state changes.
- Always clean up engagements after transfer completes or on error.
- Regenerate ephemeral keys for each session using fresh shared parameters.
- Test on real devices, since transport behavior varies across device manufacturers.