GuidesProximity (In-person)Android mDocs Verifier SDKπŸŽ“ Proximity verification

Learn how to build an Android application that can verify an mDoc presented via a proximity workflow

Overview

In this tutorial we will use the Android mDoc verifier SDK to build an Android application that can verify an mDoc presented via a proximity workflow as per ISO 18013-5:

Tutorial Workflow

  1. The credential holder presents a QR code generated by their wallet application.
  2. The verifier uses their application to scan the QR code, connect with the wallet and request an mDoc for verification.
  3. The wallet application displays matching credentials to the holder and asks for consent to share them with the verifier.
  4. The verifier application receives the wallet’s response and verifies the provided credential.
  5. Verification results are displayed to the verifier.

The result will look something like this:

To achieve this, you will build the following capabilities into your Android verifier application:

  • Initialize the SDK, so that your application can use its methods and classes.
  • Manage certificates, which enable your application to verify mDocs that were issued by trusted issuers.
  • Scan a QR code presented by a wallet application and establish a secure communication channel.
  • Send presentation requests to the wallet application, receive a presentation response and verify its content.
  • Display the results to the verifier app user.

Tutorial Steps

Prerequisites

Before we get started, let’s make sure you have everything you need.

Prior knowledge

  • The proximity verification workflow described in this tutorial is based on the ISO 18013-5 standard. If you are unfamiliar with this standard, refer to the following Docs for more information:

  • We assume you have experience developing Android native applications in Kotlin.

If you need to get a verifier solution up and running quickly with minimal development resources and in-house domain expertise, talk to us about our white-label MATTR GO Verifier which might be a good fit for you.

Assets

  • As part of your onboarding process you should have been provided with access to the following assets:
    • ZIP file which includes the required library (verifier-*version*.zip).
    • Sample Verifier app: You can use this app for reference as we work through this tutorial.

This tutorial is only meant to be used with the most recent version of the Android mDocs Verifier SDK.

Development environment

Testing devices

As this tutorial implements a proximity presentation workflow, you will need two different mobile devices to test the end-to-end result:

  • Verifier device:
    • Supported Android device to run the built Verifier application on, setup with:
      • Bluetooth access.
      • Available internet connection.
      • USB debugging enabled.
  • Holder device:
    • Mobile device with the MATTR GO Hold example app installed and setup with:
      • Biometric authentication.
      • Bluetooth access.
      • Available internet connection.

mDoc

  1. Download and install the MATTR GO Hold example app on your wallet testing device.
  2. Use the GO Hold example app to claim an mDoc by scanning the following QR code:
QR Code

Got everything? Let’s get going!

Environment setup

Tutorial Step 1

Perform the following steps to setup and configure your development environment:

Create a new project

  1. Create a new Android Studio project, using the Empty Activity template.
Create a new Android project
  1. Name the project Verifier Tutorial.
  2. Select API 24 as the Minimum SDK version.
  3. Select Kotlin DSL as the Build configuration language.

Project configuration

  1. Sync the project with Gradle files.

Add required dependencies

  1. Select the Project view.

    Project view

  2. Create a new directory named repo in your project’s folder.

  3. Unzip the verifier-*version*.zip file and copy the unzipped global folder into the new repo folder.

    Unzipped files copied

  4. Open the settings.gradle.kts file in the VerifierTutorial folder and add the following Maven repository to the dependencyResolutionManagement.repositories block:

    settings.gradle.kts
    maven {
        url = uri("repo")
    }
  5. Open the app/build.gradle.kts file in your app folder and add the following dependencies to the dependencies block:

app/build.gradle.kts
implementation("global.mattr.mobilecredential:verifier:1.0.0")
implementation("androidx.navigation:navigation-compose:2.8.4")
Replace 1.0.0 with your version of the Verifier library.

The required navigation-compose version may differ based on your version of the IDE, Gradle, and other project dependencies.

  1. In the same app/build.gradle.kts file, add the following stub values to the defaultConfig object:
app/build.gradle.kts
manifestPlaceholders["mattrDomain"] = "stub_domain"
manifestPlaceholders["mattrScheme"] = "stub_scheme"

These are required to enable the OpenID4VCI workflow used to issue credentials into the app.

  1. Sync the project with Gradle files.

  2. Open the Build tab and select Sync to make sure that the project has synced successfully.

    Synced successfully

Run the application

  1. Connect a debuggable mobile device to your machine.

  2. Build and run the app on the connected mobile device.

    The app should launch with a β€œHello, Android!” text displayed:

    Blank app

Nice work, your application is now all set to begin using the SDK!

Initialize the SDK

Tutorial Step 2

The first capability you will build into your app is to initialise the SDK so that your app can use its functions and classes. To achieve this, we need to initialise the MobileCredentialVerifier class.

Create the application structure

Open the app/src/main/*your.package*/MainActivity.kt file in your project and replace any existing code with the following:

MainActivity.kt
package com.example.verifiertutorial
 
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.verifiertutorial.ui.theme.VerifierTutorialTheme
import global.mattr.mobilecredential.common.dto.MobileCredentialResponse
import global.mattr.mobilecredential.verifier.MobileCredentialVerifier
import kotlinx.coroutines.launch
 
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Step 1: Initialize the SDK
        enableEdgeToEdge()
        setContent {
            VerifierTutorialTheme {
                val navController = rememberNavController()
                NavHost(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(vertical = 72.dp, horizontal = 8.dp),
                    startDestination = "home",
                    navController = navController,
                ) {
                    composable("home") {
                        HomeScreen(navController)
                    }
                    composable("certManagement") {
                        // Step 2.1: Add certificates management screen call
                    }
                    composable("scanOffer") {
                        // Step 3.1: Add "Scan Offer" screen call
                    }
                    composable("viewResponse") {
                        // Step 3.10: Add "View Response" screen call
                    }
                }
            }
        }
    }
}
 
@Composable
fun HomeScreen(navController: NavController) {
    Column(modifier = Modifier.fillMaxWidth()) {
        Button(
            modifier = Modifier.fillMaxWidth(),
            onClick = { navController.navigate("certManagement") }
        ) {
            Text("Certificate Management")
        }
        Button(
            modifier = Modifier.fillMaxWidth(),
            onClick = { navController.navigate("scanOffer") }
        ) {
            Text("Scan Presentation Offer")
        }
    }
}
 
// Step 3.3: Add shared data

This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step.

Initialize the MobileCredentialVerifier class

  1. Add the following code after the // Step 1: Initialize the SDK comment to initialise the SDK:

    We recommend copying and pasting the comment text (e.g. // Step 1: Initialize the SDK) to easily locate it in the code.

    MainActivity.kt
    lifecycleScope.launch {
        MobileCredentialVerifier.getInstance().initialise(this@MainActivity)
    }
  2. Resolve any unresolved references, as the IDE prompts:

    Reference resolution

  3. Run the app to make sure it compiles properly.

Manage certificates

Tutorial Step 3

Once the SDK is initialized, the next step is to build the capability for the application to manage certificates.

Tutorial Workflow

Every mDoc is signed by a series of certificates, referred to as a chain of trust. For your application to verify a presented mDoc it must validate it was signed using a root certificate (IACA) associated with a trusted issuer.

To enable this, your application can provide an interface for the user to manage (add, view and remove) certificates. We will achieve this by creating a new CertManagementScreen and building certificate management capabilities into it.

Create the Certificate Management screen

  1. In your package, create a new file CertManagementScreen.kt.

    Cert management screen created

  2. Add the following code to the file to display the basic UI that enables the user to add, view and remove certificates:

    CertManagementScreen.kt
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.material.icons.Icons
    import androidx.compose.material.icons.filled.Delete
    import androidx.compose.material3.Card
    import androidx.compose.material3.Icon
    import androidx.compose.material3.IconButton
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.material3.TextButton
    import androidx.compose.material3.TextField
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.LaunchedEffect
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import global.mattr.mobilecredential.common.dto.TrustedCertificate
    import global.mattr.mobilecredential.verifier.MobileCredentialVerifier
     
    @Composable
    fun CertManagementScreen() {
        Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(4.dp)) {
            var certText by remember { mutableStateOf("") }
            var storedCerts by remember { mutableStateOf(listOf<TrustedCertificate>()) }
            // Step 2.2: Get instance of mdocVerifier
     
            // Step 2.5: Load certificates when screen enters the composition
     
            Text("IACA CERT", style = MaterialTheme.typography.titleMedium)
            TextField(
                modifier = Modifier.fillMaxWidth(),
                value = certText,
                onValueChange = { certText = it },
                singleLine = true
            )
            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
                // Step 2.3: Add "Add certificate" button
     
                TextButton(onClick = { certText = "" }) { Text("Clear") }
            }
     
            Text("STORED CERTIFICATES", style = MaterialTheme.typography.titleMedium)
            LazyColumn(
                modifier = Modifier.fillMaxWidth(),
                verticalArrangement = Arrangement.spacedBy(6.dp)
            ) {
                items(storedCerts, key = { it.id }) { cert ->
                    Row(modifier = Modifier.fillMaxWidth()) {
                        Card(Modifier.weight(1f).height(85.dp)) {
                            Text(cert.pem, Modifier.padding(6.dp))
                        }
     
                        // Step 2.4: Add "Delete certificate" button
                    }
                }
            }
        }
    }
  3. Back in the MainActivity file, add the following code under the // Step 2.1: Add certificates management screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    CertManagementScreen()
  4. Run the app and select the Certificate Management button. You should be navigated to the new certificate management screen, where you can see controls that would enable the user to add, view and remove certificates. We will build these capabilities into the controls in the next step.

Add Certificate Management functionalities

Currently our CertificateManagementView view has no functionalities. Let’s fix this by adding the capabilities to add, view and remove certificates.

  1. In the CertificateManagementView fil,e add the following code under the // Step 2.2: Get instance of mdocVerifier comment to get an instance of MobileCredentialVerifier in our composable for further usage:

    CertManagementScreen.kt
     val mdocVerifier = remember { MobileCredentialVerifier.getInstance() }
  2. Add the following code under the // Step 2.3: Add "Add certificate" button comment to add a button, that will be responsible for adding the certificate, entered in the text field above:

    CertManagementScreen.kt
     TextButton(
         onClick = {
             mdocVerifier.addTrustedIssuerCertificates(listOf(certText))
             storedCerts = mdocVerifier.getTrustedIssuerCertificates()
         }
     ) { Text("Add") }
  3. Add the following code under the // Step 2.4: Add "Delete certificate" button comment to add a button, that will delete the certificate, when pressed:

    CertManagementScreen.kt
     IconButton(
         onClick = {
             mdocVerifier.deleteTrustedIssuerCertificate(cert.id)
             storedCerts = mdocVerifier.getTrustedIssuerCertificates()
         },
         modifier = Modifier.align(Alignment.CenterVertically)
     ) {
         Icon(
             Icons.Default.Delete,
             contentDescription = "Delete Cert",
             tint = MaterialTheme.colorScheme.primary
         )
     }
  4. Add the following code under the // Step 2.5: Load certificates when screen enters the composition comment to enable loading the certificates from the local storage and showing them, when the screen enters the composition:

    CertManagementScreen.kt
     LaunchedEffect(Unit) {
         storedCerts = mdocVerifier.getTrustedIssuerCertificates()
     }
  5. Run the app and perform the following instructions:

    1. Select the Manage Certificates button.

    2. Copy and paste the following text into the IACA Certificate text box.

      MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG
      EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew
      HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp
      MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq
      hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp
      dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud
      EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq
      232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu
      bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp
      ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp
      bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny
      bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI
      SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6

You will need to copy this text from the device you are using to display this tutorial and paste it in the device where you are running the built application. This can be achieved by navigating to this tutorial on your testing mobile device.

  1. Select the Add button. The new certificate should appear in the STORED CERTIFICATES area.

  2. Press Delete icon to remove the certificate.

You should get a result similar to the following:

  1. When the user selects the Certificate Management button they are navigated to the CertManagementScreen.
  2. When the user adds the text and selects the Add button a new certificate is added and displayed in the STORED CERTIFICATES area.
  3. When the user selects the Delete icon the certificate is removed.

You have now built the capabilities required to manage certificates in your verifier application. Now we can proceed to building the capabilities to handle the actual presentation workflow.

Verify mDocs

Tutorial Step 4

In this part we will build the components that enable a verifier app to verify an mDoc presented via a proximity workflow as per ISO 18013-5:

Tutorial Workflow

To achieve this, your application must be able to:

  1. Create a presentation request that defines the information required for verification.
  2. Scan and process a QR code created presented by a wallet application.
  3. Establish a secure connection with the wallet over Bluetooth, share the presentation request and receive a response.
  4. Verify the response and display the results to the verifier app user.

Create a screen for scanning the credential offer

  1. In your package, create a new file called ScanOfferScreen.kt and add the following code:

    ScanOfferScreen.kt
    import android.app.Activity
    import android.Manifest
    import android.widget.Toast
    import androidx.activity.compose.rememberLauncherForActivityResult
    import androidx.activity.result.contract.ActivityResultContracts
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material3.CircularProgressIndicator
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.DisposableEffect
    import androidx.compose.runtime.LaunchedEffect
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.rememberCoroutineScope
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.platform.LocalContext
    import androidx.compose.ui.viewinterop.AndroidView
    import androidx.navigation.NavController
    import com.google.accompanist.permissions.ExperimentalPermissionsApi
    import com.google.accompanist.permissions.isGranted
    import com.google.accompanist.permissions.rememberPermissionState
    import com.journeyapps.barcodescanner.BarcodeCallback
    import com.journeyapps.barcodescanner.DecoratedBarcodeView
    import global.mattr.mobilecredential.common.deviceretrieval.devicerequest.DataElements
    import global.mattr.mobilecredential.common.deviceretrieval.devicerequest.NameSpaces
    import global.mattr.mobilecredential.common.dto.MobileCredentialRequest
    import global.mattr.mobilecredential.verifier.MobileCredentialVerifier
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.launch
     
    @OptIn(ExperimentalPermissionsApi::class)
    @Composable
    fun ScanOfferScreen(activity: Activity, navController: NavController) {
        // Step 3.6: Add permission request logic
    }
     
    // Step 3.5: Add screen content
     
    // Step 3.4: Add QR scan callback
     
    // Step 3.2: Create a sample request
  2. Back in the MainActivity file, add the following code under the // Step 3.1: Add "Scan Offer" screen call to connect the created composable to the navigation graph:

    MainActivity.kt
    ScanOfferScreen(this@MainActivity, navController)

Create a presentation request

As a verifier, you can select what information you request for verification. Our application implements this by creating a MobileCredentialRequest instance to define the required information, and a new variable to hold the response from the wallet application.

  1. In the ScanOfferScreen.kt file, add the following code under the // Step 3.2: Create a sample request comment to define what information to request from the holder’s application:

    ScanOfferScreen.kt
     private val sampleMdocRequest = MobileCredentialRequest(
         docType = "org.iso.18013.5.1.mDL",
         namespaces = NameSpaces(
             mapOf(
                 "org.iso.18013.5.1" to DataElements(
                     listOf("given_name", "family_name", "birth_date").associateWith { false }
                 )
             )
         )
     )

    This object details:

    • The requested credential type (e.g. org.iso.18013.5.1.mDL).
    • The claims required for verification (e.g. given_name).
    • The requested namespace (e.g. org.iso.18013.5.1).
    • Whether or not the verifier intends to persist the claim value (true/false).

    For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the birth_date under the org.iso.18013.5.1 namespace. If a wallet responds to this request with a credential that includes a birth_date but rather under the org.iso.18013.5.1.US namespace, the claim will not be verified.

To simplify the tutorial, this is a hardcoded request. However, once you are comfortable with the basic functionalities you can create a UI in your verifier application that enables the user to create different requests on the fly by selecting different claims to include. See our GO Verify app as an example.

  1. Back in the MainActivity.kt file, add the following code under the // Step 3.3: Add shared data comment to create a new credentialResponse variable that will hold the response from the holder’s application:

    MainActivity.kt
     object SharedData {
     
         var credentialResponse: MobileCredentialResponse? = null
     }

Now your application has an existing request to share, and a variable to hold any incoming responses. We can now proceed to build the capabilities to send the request and handle the response.

Scan and process a QR code

Tutorial Workflow

As defined in ISO/IEC 18130-5:2021, a proximity presentation workflow is always initiated by the holder (wallet application user), who must create a QR code for the verifier to scan in order to initiate the device engagement phase.

Tutorial Workflow

This means that your verifier application must be able to scan and process this QR code. For ease of implementation, we will use a third party framework to achieve this.

  1. Add dependencies to your app/build.gradle.kts:

    app/build.gradle.kts
    implementation("com.google.accompanist:accompanist-permissions:0.36.0")
    implementation("com.journeyapps:zxing-android-embedded:4.3.0")
  2. Sync project with Gradle files.

  3. In the ScanOfferScreen.kt file, add the following code under the // Step 3.4: Add QR scan callback comment to define a callback that is called when the QR code was successfully scanned (we will implement the callback logic at a later stage):

    ScanOfferScreen.kt
     private fun onQrScanned(
         activity: Activity,
         deviceEngagement: String,
         coroutineScope: CoroutineScope,
         navController: NavController
     ) {
         coroutineScope.launch {
             // Step 3.7: Get MobileCredentialVerifier object
     
             // Step 3.8: Request credentials
     
             // Step 3.9: Handle response
         }
     }
  4. Add the following code under the // Step 3.5: Add screen content comment to define the main UI of the screen:

    ScanOfferScreen.kt
     @Composable
     private fun Content(activity: Activity, navController: NavController) {
         val context = LocalContext.current
         val barcodeView = remember { DecoratedBarcodeView(context) }
         val coroutineScope = rememberCoroutineScope()
         var isQrScanned by remember { mutableStateOf(false) }
     
         val barcodeCallback = remember {
             BarcodeCallback { result ->
                 onQrScanned(activity, result.text, coroutineScope, navController)
                 barcodeView.pause()
                 isQrScanned = true
             }
         }
     
         DisposableEffect(Unit) {
             barcodeView.decodeContinuous(barcodeCallback)
             barcodeView.resume()
             onDispose { barcodeView.pause() }
         }
     
         if (!isQrScanned) {
             AndroidView(factory = { barcodeView }, modifier = Modifier.fillMaxSize())
         } else {
             Box(Modifier.fillMaxSize()) {
                 CircularProgressIndicator(Modifier.align(Alignment.Center))
             }
         }
     }
  5. Add the following code under the // Step 3.6: Add permission request logic comment to define a basic logic for requesting the camera access permission at runtime:

    ScanOfferScreen.kt
    val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
     
    val requestPermissionLauncher =
        rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {}
     
    LaunchedEffect(cameraPermissionState) {
        if (!cameraPermissionState.status.isGranted) {
            requestPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
    }
     
    if (cameraPermissionState.status.isGranted) Content(activity, navController)
    1. Run the app and select the Scan QR Code button. You should be navigated to the new QRScannerView where you can use the camera to scan a QR code.

Now we will build the logic that handles this QR code to establish a secure connection with the wallet application.

Exchange presentation request and response

Tutorial Workflow

Once the verifier has scanned the QR code presented by the wallet application, your application must retrieve the information from that QR code and use it to establish a secure connection between the verifier and holder devices.

The verifier then uses this secure connection to send a presentation request to which the holder wallet application responds with a presentation response. The SDK then verifies any mDocs included in the response and stores the verification results in a variable.

To achieve this, your application must use the SDK’s createProximityPresentationSession function that takes a string retrieved from the QR code and uses it to establish a proximity presentation session with the wallet application and initiate the presentation workflow.

  1. Add the following code under the // Step 3.7: Get MobileCredentialVerifier object to get an instance of the MobileCredentialVerifier:

    ScanOfferScreen.kt
    val mdocVerifier = MobileCredentialVerifier.getInstance()
  2. Add the following code under the // Step 3.8: Request credentials to define the logic of requesting the mobile credentials from the Wallet. The below code:

    1. Establishes a secure connection with the wallet application by creating a ProximityPresentationSession instance.
    2. Calls requestMobileCredentials function of the ProximityPresentationSession , which accepts a list of MobileCredentialRequests , sends the requests to the wallet application, receives a response from the wallet application, and verifies any mDocs included in the response.
    3. Stores the response in the SharedData.credentialResponse value.
    4. Handles the exceptions, if they were thrown from the above calls. An exception can be thrown if, for example, the Bluetooth connection between the Holder and Verifier devices was interrupted during the session.
    ScanOfferScreen.kt
    SharedData.credentialResponse = try {
        val session = mdocVerifier
            .createProximityPresentationSession(activity, deviceEngagement)
     
        session.requestMobileCredentials(listOf(sampleMdocRequest), skipStatusCheck = true)
            .also { session.terminateSession() }
    } catch (e: Exception) {
        Toast.makeText(activity, "Failed to request credentials", Toast.LENGTH_SHORT).show()
        null
    }

Now that we have the verification results stored, you can implement different business logics to handle the results.

For this tutorial, we will display these results to the verifier app user, individually indicating the verification status of each claim included in the request.

Display verification results

Tutorial Workflow

We will now create a new screen that is automatically displayed when verification results are available.

  1. Add the following code under the // Step 3.9: Handle response to navigate the user to the response screen, where they can see the retrieved credentials, if the retrieval was successful:

    ScanOfferScreen.kt
    // Step 3.9: Handle response
    SharedData.credentialResponse?.let {
       navController.navigate("viewResponse") { popUpTo("home") }
    }
  2. Create a new file named ViewResponseScreen.kt that will be used to display the response to the verifier application user.

  3. Copy and paste the following code into the new file:

    ViewResponseScreen.kt
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.ColumnScope
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.Spacer
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.rememberScrollState
    import androidx.compose.foundation.verticalScroll
    import androidx.compose.material3.Card
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.text.style.TextOverflow
    import androidx.compose.ui.unit.dp
    import global.mattr.mobilecredential.common.deviceretrieval.deviceresponse.DataElementIdentifier
    import global.mattr.mobilecredential.common.deviceretrieval.deviceresponse.NameSpace
    import global.mattr.mobilecredential.common.dto.MobileCredentialElement
     
    @Composable
    fun ViewResponseScreen() {
        // Step 3.11: Define content
    }
     
    // Step 3.14: Display claims
     
    // Step 3.13: Map a claim or an error to string
  4. Back in the MainActivity.kt file, add the following code under the // Step 3.10: Add "View Response" screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    ViewResponseScreen()
  5. Return to the ViewResponseScreen.kt screen and add the following code under the // Step 3.11: Define content comment to define the basic UI for displaying the response details to the verifier application user:

    ViewResponseScreen.kt
    val credential = SharedData.credentialResponse?.credentials?.firstOrNull()
    if (credential == null || SharedData.credentialResponse?.credentialErrors != null) {
        // Step 3.12: Show error
    } else {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState()),
            verticalArrangement = Arrangement.spacedBy(4.dp),
        ) {
            // Step 3.16: Show credential verification status
     
            // Step 3.15: Show retrieved claims and errors
        }
    }

While our SDK allows to request multiple document types (and thus, multiple credentials) at the same time, for the tutorial simplicity we requested only one document type, and expect to see only one mobile credential as the response. Because of that, we take and handle only the first element from the retrieved credentials list.

  1. Add the following code under the // Step 3.12: Show error comment to show an error message in case the response is empty or if there were major errors during the response retrieval:

    ViewResponseScreen.kt
    Box(Modifier.fillMaxSize()) {
        Text("There were errors while receiving the response", Modifier.align(Alignment.Center))
    }
  2. Add the following code under the // Step 3.13: Map a claim or an error to string comment to map the received claim value (or a claim error) to a string:

    ViewResponseScreen.kt
    private fun Any.claimToUiString() = when (this) {
        is MobileCredentialElement -> {
            when (this) {
                is MobileCredentialElement.ArrayElement, is MobileCredentialElement.DataElement,
                is MobileCredentialElement.MapElement -> this::class.simpleName ?: "Unknown element"
     
                else -> value.toString()
            }
        }
     
        else -> "Not returned"
    }

Claim error here means that the presentation session has completed successfully, without interruption, and the mobile credentials were received and verified, but some of the claim values were not sent to the verifier. Usually this can happen if they were absent in the document on the Holder side. For example, Verifier requested family name, given name, date of birth, and photo, but the document on the Holder contained only family name, given name_, and date of birth. Another case is if the wallet user did not give the consent for sharing some of the claims.

  1. Add the following code under the // Step 3.14: Display claims comment to create a function that displays the retrieved and failed claims to the verifier application user:

    ViewResponseScreen.kt
    @Composable
    private fun ColumnScope.Claims(
        title: String,
        claims: Map<NameSpace, Map<DataElementIdentifier, Any>>?
    ) {
        Text(
            title,
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.CenterHorizontally),
            style = MaterialTheme.typography.titleLarge
        )
        claims?.forEach { (namespace, claims) ->
            Card {
                Column(Modifier.padding(6.dp)) {
                    Text(
                        namespace,
                        style = MaterialTheme.typography.titleMedium,
                        modifier = Modifier.padding(vertical = 4.dp)
                    )
                    claims.forEach { (name, value) ->
                        Row {
                            Text(name,
                                Modifier
                                    .weight(1f)
                                    .padding(end = 4.dp))
                            Text(value.claimToUiString(), overflow = TextOverflow.Ellipsis)
                        }
                    }
                }
            }
        } ?: Text("Nothing here")
    }
  2. Add the following code under the // Step 3.15: Show retrieved claims and errors comment to create the UI for showing the retrieved and failed claims on the screen:

    ViewResponseScreen.kt
    Claims("Received claims", credential.claims)
    Spacer(Modifier.padding(8.dp))
    Claims("Failed claims", credential.claimErrors)
  3. Add the following code under the // Step 3.16: Show credential verification status comment to show the overall verification status:

    ViewResponseScreen.kt
    val statusStyle = MaterialTheme.typography.titleLarge
    if (credential.verificationResult.verified) {
        Text("Verified", style = statusStyle, color = Color.Green)
    } else {
        Text("Not verified", style = statusStyle, color = Color.Red)
    }

Test the end-to-end workflow

Tutorial Step 5

  1. Run the app.

    1. Select the Certificate Management button.

    2. Copy and paste the following text into the IACA Certificate text box.

      MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG
      EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew
      HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp
      MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq
      hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp
      dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud
      EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq
      232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu
      bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp
      ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp
      bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny
      bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI
      SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6

You will need to copy this text from the device you are using to display this tutorial and paste it in the device where you are running the built application.

  1. Select the Add button.
  2. Return to the app main screen.
  3. Open your wallet testing device and launch the GO Hold example app.
  4. Select the Wallet button.
  5. Locate the mDoc claimed earlier in this tutorial and select the share button to display a QR code.
  6. Use your verifier testing device and select the Scan QR Code button.
  7. Use the verifier testing device to scan the QR code displayed on the wallet testing device.
  8. Use the wallet testing device to consent to sharing the information with the verifier.

You should see a result similar to the following:

  1. The verifier app user adds a new certificate to the app. This is the certificate associated with the issuer of the mDoc we are about to verify.
  2. The wallet app user creates a QR code to initiate the proximity presentation workflow.
  3. The verifier app scans the QR code, establishes a secure connection and sends a presentation request.
  4. The wallet app user reviews the presentation request and agrees to share matching mDocs with the verifier.
  5. The verifier app receives the mDocs included in the presentation response.
  6. The verifier app user views the verification results.

Congratulations! Your verifier application can now verify mDocs presented via a proximity presentation workflow, as per ISO 18013-5.

Summary

You have just used the Android mDoc verifier SDK to build an Android application that can verify an mDoc presented via a proximity workflow as per ISO 18013-5:

Tutorial Workflow

This was achieved by building the following capabilities into the application:

  • Initialize the SDK, so that your application can use its functions and classes.
  • Manage certificates, which enable your application to verify mDocs that were issued by trusted issuers.
  • Scan a QR code presented by a wallet application and establish a secure communication channel.
  • Send presentation requests to the wallet application, receive a presentation response and verify its content.
  • Display the results to the verifier app user.

What’s next?

  • You can check out the Android mDoc verifier SDK Docs to learn more about available functions and classes.