GuidesAndroid mDocs Holder SDK๐ŸŽ“ Claim a credential

Learn how to build an Android application that can claim an mDoc via OID4VCI

Introduction

In this tutorial we will use the Android mDoc holder SDK to build an Android application that can claim an mDoc issued via an OID4VCI workflow:

OID4VCI Tutorial Workflow

  1. The user launches the application and scans a QR code received from an issuer.
  2. The application displays what credential is being offered to the user and by what issuer.
  3. The user agrees to claiming the offered credential.
  4. The user is redirected to complete authentication.
  5. Upon successful authentication, the credential is issued to the userโ€™s application, where they can now store, view and present it.

The result will look something like this:

Prerequisites

Before we get started, letโ€™s make sure you have everything you need.

Prior knowledge

  • The issuance workflow described in this tutorial is based on the OID4VCI specification. If you are unfamiliar with this specification, refer to our Docs section for more information:

  • We assume you have experience developing Android apps in Kotlin.

If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, talk to us about our white-label MATTR GO Hold app 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 (holder-*version*.zip).
    • Sample Holder 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 Holder SDK.

Development environment

Testing device

  • Supported Android device to run the built application on, setup with:
    • Biometric authentication (Face recognition, fingerprint recognition).
    • Available internet connection.
    • Debugging enabled.

Got everything? Letโ€™s get going!

Environment setup

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 Holder 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 holder-*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 HolderTutorial 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:holder:1.0.0")
    implementation("androidx.navigation:navigation-compose:2.8.4")

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 values to the defaultConfig block. These represent the Authentication provider we will use for this tutorial:

    app/build.gradle.kts
    manifestPlaceholders["mattrScheme"] = "io.mattrlabs.sample.mobilecredentialtutorialholderapp"
    manifestPlaceholders["mattrDomain"] = "credentials"

    These values are part of the redirect URI the SDK will redirect the user to once they complete Authentication with the issuer:

    • mattrScheme : The scheme can be any path that is handled by your application and registered with the issuer.
    • mattrDomain : The domain can be any path, but our best practice recommendation is to configure this to be credentials, as the standard format for the redirect URI is {redirect.scheme}://credentials/callback.

These values (alongside the client ID, which will be discussed later) must be registered as part of the issuerโ€™s OID4VCI workflow redirect URI. For this tutorial you will be claiming a credential from a MATTR Labs issuer which is already configured with the parameters detailed above. We will help you configure your unique values as you move your implementation into production.

  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 Android 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!

Tutorial steps

To enable a user to interact with an OID4VCI Credential offer and claim an mDoc, you will build the following capabilities into your application:

  1. Initialize the SDK.
  2. Interact with a Credential offer.
  3. Retrieve offer details and present them to the Holder.
  4. Obtain user consent and initiate Credential issuance.

Initialize the SDK

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

  1. Open the MainActivity file in your project and replace any existing code with the following:

    MainActivity.kt
    package com.example.holdertutorial
     
    import android.app.Activity
    import android.os.Bundle
    import android.widget.Toast
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.Arrangement
    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.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.material3.Button
    import androidx.compose.material3.Card
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.rememberCoroutineScope
    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.holdertutorial.ui.theme.HolderTutorialTheme
    import global.mattr.mobilecredential.common.dto.MobileCredential
    import global.mattr.mobilecredential.holder.MobileCredentialHolder
    import global.mattr.mobilecredential.holder.ProximityPresentationSession
    import global.mattr.mobilecredential.holder.issuance.dto.DiscoveredCredentialOffer
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.launch
     
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // Claim Credential - Step 1.2: Initialize the SDK
            setContent {
                HolderTutorialTheme {
                    val navController = rememberNavController()
                    NavHost(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(8.dp),
                        startDestination = "home",
                        navController = navController,
                    ) {
                        composable("home") {
                            HomeScreen(this@MainActivity, navController)
                        }
                        composable("scanOffer") {
                            // Claim Credential - Step 2.5: Add "Scan Offer" screen call
                        }
                        composable("retrievedCredential") {
                            // Claim Credential - Step 4.8: Add "Retrieved Credential" screen call
                        }
                        composable("presentationQr") {
                            // Proximity Presentation - Step 1.2: Add "QR Presentation" screen call
                        }
                        composable("presentationSelectCredentials") {
                            // Proximity Presentation - Step 2.6: Add "Select Credential" screen call
                        }
                    }
                }
            }
        }
    }
     
    @Composable
    fun HomeScreen(activity: Activity, navController: NavController) {
        val coroutineScope = rememberCoroutineScope()
     
        Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
            Button(onClick = { navController.navigate("scanOffer") }, Modifier.fillMaxWidth()) {
                Text("Scan Credential Offer")
            }
     
            // Proximity Presentation - Step 1.3: Add button for starting the credentials presentation workflow
     
            // Claim Credential - Step 3.3: Display discovered credential offer
        }
    }
     
    // Claim Credential - Step 4.3: Create function to retrieve credentials
     
    // Claim Credential - Step 4.2: Add OpenID4VCI constants object
     
    object SharedData {
        // Claim Credential - Step 3.1: Add discovered credential offer variable
        // Claim Credential - Step 4.1: Add retrieved credentials variable
        // Proximity Presentation - Step 2.1: Add proximity presentation request variable
    }

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.

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

  1. Add the following code after the // Claim Credential - Step 1.2: Initialize the SDK comment to initialize the SDK by creating a new instance of the MobileCredentialHolder class:

    MainActivity.kt
    lifecycleScope.launch {
        MobileCredentialHolder.getInstance().initialise(this@MainActivity)
    }

This will initialize the SDK, making it available for your application.

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

Interact with a Credential offer

Interact with Credential offer

Once the SDK is initialized, the next step is to build the capability to handle a Credential offer.

Users can receive OID4VCI Credential offers as deep-links or QR codes. In this tutorial we will use the following QR code, which is a MATTR Labs example of an OID4VCI Credential offer:

QR Code

Creating your own Credential offer is not within the scope of the current tutorial. You can follow the OID4VCI guide that will walk you through creating one.

Your application must provide a way for the user to interact with Credential offers. As this tutorial uses an offer formatted as a QR code, your application needs to be able to scan and process it. For ease of implementation, we will use a third party library to achieve this:

  1. Add the following dependencies to your application app/build.gradle.kts file:

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

  3. In your package, create a new file named ScanOfferScreen.kt.

    Scan Offer screen created

  4. Add the following code to the new file:

    ScanOfferScreen.kt
    import android.Manifest
    import android.app.Activity
    import android.content.Context
    import android.util.Log
    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.holder.MobileCredentialHolder
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.launch
     
    // Gets the permissions and shows the screen content, when the permissions are obtained
    @OptIn(ExperimentalPermissionsApi::class)
    @Composable
    fun ScanOfferScreen(navController: NavController) {
        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(navController)
    }
     
    // Screen content
    @Composable
    private fun Content(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 ->
                // Executed when the QR code was scanned
                coroutineScope.launch { onQrScanned(context, result.text, navController) }
                barcodeView.pause()
                isQrScanned = true
            }
        }
     
        // Setting up the QR scanner
        DisposableEffect(Unit) {
            barcodeView.decodeContinuous(barcodeCallback)
            barcodeView.resume()
            onDispose { barcodeView.pause() }
        }
     
        // Showing the scanner until the QR is scanned. Showing a progress bar after that
        if (!isQrScanned) {
            AndroidView(factory = { barcodeView }, modifier = Modifier.fillMaxSize())
        } else {
            Box(Modifier.fillMaxSize()) {
                CircularProgressIndicator(Modifier.align(Alignment.Center))
            }
        }
    }
     
    private suspend fun onQrScanned(context: Context, offer: String, navController: NavController) {
        // Step 3.2: Discover credential offer
    }
  5. Back in the MainActivity file, add the following code under the // Claim Credential - Step 2.5: Add Scan Offer screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    ScanOfferScreen(navController)
  6. Run the app and select the Scan Credential Offer button.

As the user selects the Scan Credential Offer button, the app asks for camera permission, and launches the device camera to enable the user to scan a QR code.

Retrieve offer details and present them to the user

Present offer details

The next capability to build is for the user to be able to review the Credential offer details before agreeing to claim any credentials. Credential discovery is the process in which an application retrieves the offer details, including:

  • What Issuer is offering the credentials?
  • What credentials are being offered, in what format and what claims do they include?

To display this information to the user, your application should call the discoverCredentialOffer function.

  1. In your MainActivity file, add the following code under the // Claim Credential - Step 3.1: Add discovered credential offer variable comment to add a new variable that will hold the credential offer:

    MainActivity.kt
    var discoveredCredentialOffer: DiscoveredCredentialOffer? = null
  2. In your ScanOfferScreen file, add the following code under the // Step 3.2: Discover credential offer comment to handle the credential offer upon scanning a QR code:

    ScanOfferScreen.kt
    try {
        SharedData.discoveredCredentialOffer =
            MobileCredentialHolder.getInstance().discoverCredentialOffer(offer)
    } catch (e: Exception) {
        Toast.makeText(context, "Failed to discover offer", Toast.LENGTH_SHORT).show()
    }
     
    navController.navigateUp()

    Now, once the user scans a QR Code, the discoverCredentialOffer function is called and accepts the returned offer string as its parameter.

    This is a URL-encoded Credential offer which in our example was embedded in a QR code. In other implementations you might have to retrieve this parameter from a deep-link.

    The discoverCredentialOffer function makes a request to the offer URL to retrieve the offer information and returns it as a DiscoveredCredentialOffer object:

    @Serializable
    data class DiscoveredCredentialOffer(
        val issuer: String,
        val authorizeEndpoint: String,
        val tokenEndpoint: String,
        val credentialEndpoint: String,
        val credentials: List<OfferedCredential>,
        val mdocIacasUri: String,
        requestParameters: RequestParameters?
    )

    The application can now use the issuer and credentials properties and present this information for the user to review.

  3. In your MainActivity file, add the following code under the // Claim Credential - Step 3.3: Display discovered credential offer to display the offer details to the user:

    MainActivity.kt
    SharedData.discoveredCredentialOffer?.let { discoveredOffer ->
        Text("Received Credential Offer from ${discoveredOffer.issuer}")
        LazyColumn(
            Modifier
                .fillMaxWidth()
                .weight(1f)
        ) {
            items(discoveredOffer.credentials, key = { it.doctype }) { credential ->
                Card(Modifier.fillMaxWidth()) {
                    Column(Modifier.padding(4.dp)) {
                        Text("Name: ${credential.name ?: ""}")
                        Text("DocType: ${credential.doctype}")
                    }
                }
            }
        }
     
        // Claim Credential - Step 4.4: Add consent button
    }
  4. Run the app, select the Scan Credential Offer button and scan the following QR code:

QR Code

You should see a result similar to the following:

As the user scans the QR code, the application displays the Credential offer details.

Present offer details

The next (and final!) step is to build the capability for the user to accept the credential offer based on the displayed information. This should then trigger issuing the credential and storing it in the application.

In our example this is achieved by selecting a Consent and retrieve Credential(s) button. Once the user provides their consent by selecting this button, your application must call the retrieveCredentials function to trigger the credential issuance.

  1. In your MainActivity file, add the following code under the // Claim Credential - Step 4.1: Add retrieved credentials variable comment to add a new variable that will hold the result returned by the retrieveCredentials function:

    MainActivity.kt
    var retrievedCredentials: List<MobileCredential> = emptyList()
  2. Add a new Constants object under the // Claim Credential - Step 4.2: Add OpenID4VCI constants object comment to add the values that will be used for the retrieveCredentials call:

    MainActivity.kt
    object Constants {
        const val CLIENT_ID =
            "android-mobile-credential-tutorial-holder-app"
     
        const val REDIRECT_URI =
            "io.mattrlabs.sample.mobilecredentialtutorialholderapp://credentials/callback"
    }
    • CLIENT_ID : This identifier is used by the issuer to recognize the application. This is only used internally in the interaction between the application and the issuer and can be any string as long as it is registered with the issuer as a trusted application.
    • REDIRECT_URI : Constructed of the same scheme and domain values we have set as the manifest placeholders in the app/build.gradle.kts file.
  3. Add the following code under the // Claim Credential - Step 4.3: Create function to retrieve credentials comment to create a new function that will call the retrieveCredentials method when the user gives the consent for the credentials retrieval:

    MainActivity.kt
    private fun onRetrieveCredentials(
        coroutineScope: CoroutineScope,
        activity: Activity,
        discoveredOffer: DiscoveredCredentialOffer,
        navController: NavController
    ) {
        coroutineScope.launch {
            try {
                val mdocHolder = MobileCredentialHolder.getInstance()
                val retrieveCredentialResults = mdocHolder.retrieveCredentials(
                    activity,
                    discoveredOffer,
                    Constants.CLIENT_ID,
                    Constants.REDIRECT_URI,
                    autoTrustMobileCredentialIaca = true
                )
     
                // Claim Credential - Step 4.5: Display retrieved credentials
            } catch (e: Exception) {
                Toast.makeText(activity, "Failed to retrieve credentials", Toast.LENGTH_SHORT).show()
            }
        }
    }

    Letโ€™s review where we get all the parametersโ€™ values from:

    • discoveredOffer: This is the DiscoveredCredentialOffer object returned by the discoverCredentialOffer function in step 3 above.
    • Constants.Client_ID: This was configured as a constant. It is used by the issuer to identify the application that is making a request to claim credentials.
    • Constants.REDIRECT_URI: This was configured as a constant, and also added to the build script when setting up your development environment. It is used by the SDK to redirect the user back to your application after completing authentication.
  4. Add the following code under the // Claim Credential - Step 4.4: Add consent button comment to add a button for calling the new function:

    MainActivity.kt
    Button(
        onClick = {
            onRetrieveCredentials(coroutineScope, activity, discoveredOffer, navController)
        },
        Modifier.fillMaxWidth()
    ) { Text("Consent and retrieve Credential(s)") }

Authentication

Once the new button is selected the function is called, redirecting the user to authenticate with the configured Authentication provider defined in the authorizeEndpoint element of the DiscoveredCredentialOffer object.

Upon successful authentication, the user can proceed to complete the OID4VCI workflow configured by the issuer. This workflow can include different steps based on the issuerโ€™s configuration, but eventually the user is redirected to the configured redirectUri which should be handled by your application.

In the example Credential offer used in this tutorial, the issuance workflow only includes authenticating with a mock authentication provider and claiming the credential. But check out our other guides for creating rich and flexible user experiences.

As the user is redirected to redirectUri, the issuer sends the issued mDocs to your application. The SDK then processes the received credentials and validates them against the ISO/IEC 18013-5:2021 standard. Credentials who meet validation rules are stored in the application internal data storage.

The retrieveCredentials function then returns a RetrieveCredentialResult list, which references all retrieved credentials:

[RetrieveCredentialResult]
 
@Serializable
data class RetrieveCredentialResult(
    val doctype: String,
    val credentialId: String?,
    val error: RetrieveCredentialError?
)
 
[
  {
    "doctype":"org.iso.18013.5.1.mDL",
    "credentialId":"F52084CF-8270-4577-8EDD-23149639D985"
  }
]
  • doctype : Identifies the credential type.
  • credentialId : Unique identifier (UUID) of this credential.

Your application can now retrieve specific credentials by calling the getCredential function with the credentialId of any of the retrieved credentials.

The getCredential function returns a MobileCredential object which represents the issued mDoc, and your application can now introduce UI elements to enable the user to view the credential.

  1. Add the following code under the // Claim Credential - Step 4.5: Display retrieved credentials comment to retrieve the credentials by their IDs from the local storage, save them to the SharedData.retrievedCredentials variable and navigate to the retrievedCredential screen to display the retrieved credential:

    MainActivity.kt
    SharedData.retrievedCredentials = retrieveCredentialResults.mapNotNull {
        try {
            mdocHolder.getCredential(it.credentialId!!, skipStatusCheck = true)
        } catch (e: Exception) {
            val msg = "Failed to get credential from storage"
            Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show()
            null
        }
    }
     
    navController.navigate("retrievedCredential")
    SharedData.discoveredCredentialOffer = null
  2. In your package, create a new file named RetrievedCredentialsScreen.kt.

  3. Add the following code to the new file to display the docType and claims of retrieved credentials to the user:

    RetrievedCredentialsScreen.kt
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.foundation.shape.RoundedCornerShape
    import androidx.compose.material3.Card
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import global.mattr.mobilecredential.common.deviceretrieval.deviceresponse.NameSpace
    import global.mattr.mobilecredential.common.dto.MobileCredentialElement
     
    @Composable
    fun RetrievedCredentialsScreen() {
        if (SharedData.retrievedCredentials.isEmpty()) {
            Text("No credentials received")
        } else {
            Column {
                Text(
                    "Retrieved Credentials",
                    modifier = Modifier.fillMaxWidth(),
                    style = MaterialTheme.typography.titleLarge
                )
     
                LazyColumn(Modifier.fillMaxWidth()) {
                    items(SharedData.retrievedCredentials, key = { it.id }) { credential ->
                        Document(
                            credential.docType,
                            credential.claims.mapValues { (_, claims) ->
                                claims.map { (name, value) -> "$name: ${value.toUiString()}" }.toSet()
                            }
                        )
                    }
                }
            }
        }
    }
     
    @Composable
    fun Document(
        docType: String,
        namespacesAndClaims: Map<NameSpace, Set<String>>,
        modifier: Modifier = Modifier
    ) {
        Card(
            modifier
                .fillMaxWidth()
                .padding(6.dp)) {
            Column {
                Text(docType, Modifier.padding(6.dp), style = MaterialTheme.typography.titleMedium)
                namespacesAndClaims.forEach { (namespace, claims) ->
                    Text(namespace, Modifier.padding(4.dp), style = MaterialTheme.typography.titleSmall)
                    Column(
                        Modifier
                            .padding(6.dp)
                            .fillMaxWidth()
                            .background(MaterialTheme.colorScheme.background, RoundedCornerShape(6.dp))
                            .padding(6.dp)
                    ) {
                        claims.forEach { claim -> Text(claim) }
                    }
                }
            }
        }
    }
     
    fun MobileCredentialElement.toUiString() = when (this) {
        is MobileCredentialElement.ArrayElement, is MobileCredentialElement.DataElement,
        is MobileCredentialElement.MapElement -> this::class.simpleName ?: "Unknown element"
     
        else -> value.toString()
    }
  4. Back in your MainActivity file, add the following code under the // Claim Credential - Step 4.8: Add "Retrieved Credential" screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    RetrievedCredentialsScreen()
  5. Run the app, select the Scan Credential Offer button, scan the QR code and then select Consent and retrieve Credential(s).

QR Code

You should see a result similar to the following:

As the user scans the QR code, they are then provided with the offer details by the wallet. The user then provides consent to retrieving the credentials to which the wallet responds by initiating the issuance workflow and displaying the retrieved credentials to the user.

This tutorial uses a demo MATTR Labs Credential offer to issue the credential. This offer uses a workflow that doesnโ€™t actually authenticate the user before issuing a credential, but redirects them to select the credential they wish to issue. In production implementations this must be replaced by a proper authentication mechanism to comply with the ISO/IEC 18013-5:2021 standard and the OID4VCI specification.

Credential claimed

Congratulations! Your application can now interact with an OID4VCI Credential offer to claim mDocs!

Summary

You have just used the Android mDoc holder SDK to build an application that can claim an mDoc issued via an OID4VCI workflow:

OID4VCI Tutorial Workflow

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

  1. Initialize the SDK so the application can use its functions and classes.
  2. Interact with a Credential offer formatted as a QR code.
  3. Retrieve the offer details and present them to the user.
  4. Obtain user consent and initiate the credential issuance workflow.

Whatโ€™s next?