Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
implementation("global.mattr.mobilecredential:verifier:6.1.0")
implementation("global.mattr.mobilecredential:verifier:7.0.0")
implementation("androidx.navigation:navigation-compose:2.9.0")
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import androidx.navigation.compose.rememberNavController
import com.example.verifiertutorial.ui.theme.VerifierTutorialTheme
import global.mattr.mobilecredential.verifier.dto.MobileCredentialResponse
import global.mattr.mobilecredential.verifier.MobileCredentialVerifier
import global.mattr.mobilecredential.common.platformconfig.PlatformConfiguration
import global.mattr.mobilecredential.verifier.exception.VerifierException.FailedToRegisterException
import global.mattr.mobilecredential.verifier.exception.VerifierException.InvalidLicenseException
import kotlinx.coroutines.launch
import java.net.URL

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -30,12 +34,25 @@ class MainActivity : ComponentActivity() {
// The SDK must be initialized before any other SDK functions are called.
// Initialize SDK - Step 1.1: Initialize the SDK
lifecycleScope.launch {
// This function initializes storage and performs validations to ensure the SDK is ready for use.
// Only the application context is persisted.
MobileCredentialVerifier.initialize(this@MainActivity)
// Setup certificates - Step 2.1: Add trusted issuer certificates
// The added certificates will be the trust chain roots used to verify signatures of the received credentials.
MobileCredentialVerifier.addTrustedIssuerCertificates(listOf(Iacas.mattrLabs))
// From Android Verifier SDK v7.0.0, the SDK is tethered to a MATTR VII tenant. Initialization
// registers this app instance with the tenant and obtains a license, so a PlatformConfiguration
// pointing at your tenant and Verifier Application is required.
val platformConfiguration = PlatformConfiguration(
tenantHost = URL("https://your-tenant.vii.mattr.global"),
applicationId = "<YOUR_VERIFIER_APPLICATION_ID>"
)
try {
// This function initializes storage, registers the app instance, obtains a license,
// and performs validations to ensure the SDK is ready for use.
MobileCredentialVerifier.initialize(this@MainActivity, platformConfiguration)
// Setup certificates - Step 2.1: Add trusted issuer certificates
// The added certificates will be the trust chain roots used to verify signatures of the received credentials.
MobileCredentialVerifier.addTrustedIssuerCertificates(listOf(Iacas.mattrLabs))
} catch (e: FailedToRegisterException) {
// Registration with the MATTR VII tenant failed — check connectivity and configuration.
} catch (e: InvalidLicenseException) {
// The SDK license is missing, invalid, or expired.
}
}

enableEdgeToEdge()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ import global.mattr.mobilecredential.verifier.dto.MobileCredentialElement
fun ViewResponseScreen() {
// Verify mDocs - Step 4.5: Define content
// For tutorial simplicity, only the first credential in the response is displayed, since the sample request only asks for a single document type (mDL).
// From Android Verifier SDK v7.0.0, MobileCredentialResponse.credentials and .credentialErrors
// are non-nullable lists, so we check for emptiness rather than null.
val credential = SharedData.credentialResponse?.credentials?.firstOrNull()
if (credential == null || SharedData.credentialResponse?.credentialErrors != null) {
if (credential == null || SharedData.credentialResponse?.credentialErrors?.isNotEmpty() == true) {
// Verify mDocs - Step 4.6: Show error
// Show an error if something went wrong during the retrieval
Box(Modifier.fillMaxSize()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)

// MATTR MobileCredentialVerifier SDK — resolved from the local `repo` directory.
implementation("global.mattr.mobilecredential:verifier:6.1.0")
implementation("global.mattr.mobilecredential:verifier:7.0.0")
implementation("androidx.navigation:navigation-compose:2.9.0")
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.mobileverifiertutorial.ui.theme.MobileVerifierTutorialTheme
import global.mattr.mobilecredential.verifier.MobileCredentialVerifier
import global.mattr.mobilecredential.verifier.PlatformConfiguration
import global.mattr.mobilecredential.verifier.OnlinePresentationSessionResult
import global.mattr.mobilecredential.common.platformconfig.PlatformConfiguration
import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.DataElements
import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.NameSpaces
import global.mattr.mobilecredential.verifier.dto.MobileCredentialPresentation
import global.mattr.mobilecredential.verifier.dto.MobileCredentialRequest
import global.mattr.mobilecredential.verifier.exception.VerifierException.FailedToRegisterException
import global.mattr.mobilecredential.verifier.exception.VerifierException.InvalidLicenseException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
Expand All @@ -57,16 +60,27 @@ class MainActivity : ComponentActivity() {
}
}

// Step 2.3: Setup platform configuration with the MATTR VII tenant host.
// Step 2.3: Setup platform configuration with the MATTR VII tenant host and Verifier
// Application id. From Android Verifier SDK v7.0.0, the applicationId is supplied here (it is
// no longer passed to requestMobileCredentials) and also drives SDK Tethering.
val platformConfiguration = PlatformConfiguration(
tenantHost = URL(Constants.TENANT_HOST)
tenantHost = URL(Constants.TENANT_HOST),
applicationId = Constants.APPLICATION_ID
)

// Step 2.4: Initialize the SDK. Must complete before any other SDK calls.
// Step 2.4: Initialize the SDK. Must complete before any other SDK calls. Initialization
// registers the app instance with the tenant and obtains a license, so it can throw
// FailedToRegisterException and InvalidLicenseException.
lifecycleScope.launch {
MobileCredentialVerifier.initialize(
context = this@MainActivity, platformConfiguration = platformConfiguration
)
try {
MobileCredentialVerifier.initialize(
context = this@MainActivity, platformConfiguration = platformConfiguration
)
} catch (e: FailedToRegisterException) {
// Registration with the MATTR VII tenant failed — check connectivity and configuration.
} catch (e: InvalidLicenseException) {
// The SDK license is missing, invalid, or expired.
}
}
}
}
Expand Down Expand Up @@ -135,15 +149,23 @@ class VerifierViewModel : ViewModel() {
// Step 3.2: Request credentials. The SDK launches the wallet via OID4VP and
// suspends until the wallet redirects back via the deep link registered in
// AndroidManifest.xml, at which point the response is returned here.
// From v7.0.0, applicationId is no longer passed here — it comes from the
// PlatformConfiguration supplied at initialization.
val onlinePresentationResult = MobileCredentialVerifier.requestMobileCredentials(
activity = activity,
request = listOf(mobileCredentialRequest),
applicationId = Constants.APPLICATION_ID
request = listOf(mobileCredentialRequest)
)

// Step 4.2: Surface the verified credentials to the UI.
_receivedDocuments.value =
onlinePresentationResult.mobileCredentialResponse?.credentials ?: emptyList()
// Step 4.2: Surface the verified credentials to the UI. From v7.0.0,
// OnlinePresentationSessionResult is a sealed interface with Success/Failure variants.
_receivedDocuments.value = when (onlinePresentationResult) {
is OnlinePresentationSessionResult.Success ->
onlinePresentationResult.mobileCredentialResponse?.credentials ?: emptyList()
is OnlinePresentationSessionResult.Failure -> {
// onlinePresentationResult.error is available here.
emptyList()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@
// Certificate Management screen.
//
// In a production app, you would manage these certificates dynamically rather than hardcoding them.
import Foundation

enum Constants {
static let montcliffPEM =
// From iOS Verifier SDK v6.0.0, the SDK is tethered to a MATTR VII tenant. Replace these with
// your tenant host and the Verifier Application `id` created on that tenant.
static let tenantHost = URL(string: "https://your-tenant.vii.mattr.global")!
static let applicationId = "<YOUR_VERIFIER_APPLICATION_ID>"

static let montcliffPEM =
"""
MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG
EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,25 @@ final class VerifierViewModel {
// to prepare the SDK for use. A trusted IACA certificate is also pre-loaded so the app
// can verify mDocs issued by the Montcliff DMV test issuer out of the box.
init() {
do {
mobileCredentialVerifier = MobileCredentialVerifier.shared
try mobileCredentialVerifier.initialize()
Task {
try? await mobileCredentialVerifier.addTrustedIssuerCertificates(certificates: [Constants.montcliffPEM])
mobileCredentialVerifier = MobileCredentialVerifier.shared
// From v6.0.0, initialize is asynchronous and requires a PlatformConfiguration. On first
// launch it registers this app instance with the tenant and obtains a license, so it can
// throw failedToRegister and invalidLicense.
Task {
do {
let platformConfiguration = PlatformConfiguration(
tenantHost: Constants.tenantHost,
applicationId: Constants.applicationId
)
try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration)
try? await mobileCredentialVerifier.addTrustedIssuerCertificates(certificates: [Constants.montcliffPEM])
} catch MobileCredentialVerifierError.failedToRegister {
// Registration with the MATTR VII tenant failed — check connectivity and configuration.
} catch MobileCredentialVerifierError.invalidLicense {
// The SDK license is missing, invalid, or expired.
} catch {
print(error.localizedDescription)
}
} catch {
print(error.localizedDescription)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ class DocumentViewModel {
init(from presentation: MobileCredentialPresentation) {
self.docType = presentation.docType
self.verificationResult = presentation.verificationResult.verified ? "Verified" : "Invalid"
self.verificationFailedReason = presentation.verificationResult.reason?.message
// From v6.0.0, the failure detail is exposed via `failureType` (was `reason`).
self.verificationFailedReason = presentation.verificationResult.failureType?.message

self.namespacesAndClaims = presentation.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in
let (outerKey, innerDict) = outerElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ final class VerifierViewModel: ObservableObject {
@Published var navigationPath = NavigationPath()
@Published var receivedDocuments: [MobileCredentialPresentation] = []

// From v6.0.0, the applicationId is supplied via PlatformConfiguration (it is no longer passed
// to requestMobileCredentials) and also drives SDK Tethering.
let platformConfiguration = PlatformConfiguration(
tenantHost: Constants.tenantHost
tenantHost: Constants.tenantHost,
applicationId: Constants.applicationID
)

var mobileCredentialVerifier: MobileCredentialVerifier
Expand All @@ -95,11 +98,20 @@ final class VerifierViewModel: ObservableObject {
// platform configuration (your tenant host). Trusted issuer certificates are managed
// in your MATTR VII tenant rather than in the app for this workflow.
init() {
do {
mobileCredentialVerifier = MobileCredentialVerifier.shared
try mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration)
} catch {
print(error.localizedDescription)
mobileCredentialVerifier = MobileCredentialVerifier.shared
// From v6.0.0, initialize is asynchronous and drives SDK Tethering: on first launch it
// registers this app instance with the tenant and obtains a license, so it can throw
// failedToRegister and invalidLicense.
Task {
do {
try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration)
} catch MobileCredentialVerifierError.failedToRegister {
// Registration with the MATTR VII tenant failed — check connectivity and configuration.
} catch MobileCredentialVerifierError.invalidLicense {
// The SDK license is missing, invalid, or expired.
} catch {
print(error.localizedDescription)
}
}
}

Expand All @@ -111,17 +123,24 @@ final class VerifierViewModel: ObservableObject {
Task { @MainActor in
receivedDocuments = []
do {
// From v6.0.0, applicationId is no longer passed here (it comes from
// PlatformConfiguration) and challenge is optional — omit it to let the SDK generate
// a secure challenge, or pass your own to bind the response to this request.
let onlinePresentationResult = try await mobileCredentialVerifier.requestMobileCredentials(
request: [mobileCredentialRequest],
challenge: UUID().uuidString,
applicationId: Constants.applicationID
challenge: UUID().uuidString
)
guard let receivedCredentials = onlinePresentationResult.mobileCredentialResponse?.credentials else {
let errorMessage = onlinePresentationResult.error?.message ?? "No error message"
print("No response received: \(errorMessage)")
// From v6.0.0, OnlinePresentationSessionResult is a @frozen enum with success and
// failure cases, so branch over it with switch/case instead of inspecting nullable
// fields. success carries (sessionId, challenge, mobileCredentialResponse); failure
// carries (sessionId, challenge, error).
switch onlinePresentationResult {
case .success(_, _, let mobileCredentialResponse):
receivedDocuments = mobileCredentialResponse?.credentials ?? []
case .failure(_, _, let error):
print("No response received: \(error.message)")
return
}
receivedDocuments = receivedCredentials
} catch {
print(error.localizedDescription)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ class DocumentViewModel: ObservableObject {
init(from presentation: MobileCredentialPresentation) {
self.docType = presentation.docType
self.verificationResult = presentation.verificationResult.verified ? "Verified" : "Invalid"
self.verificationFailedReason = presentation.verificationResult.reason?.message
// From v6.0.0, the failure detail is exposed via `failureType` (was `reason`).
self.verificationFailedReason = presentation.verificationResult.failureType?.message

self.namespacesAndClaims = presentation.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in
let (outerKey, innerDict) = outerElement
Expand Down
Loading