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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ Desktop.ini
######################
xcuserdata/
*.xcuserstate
*.xcframework

######################
# MATTR SDK distributions
######################
# SDKs are distributed separately and added by each developer locally.
# Don't commit the unzipped artifacts that the sample apps look for.
*/repo/global/

######################
# Mac OSX
Expand Down
72 changes: 72 additions & 0 deletions android-remote-verification-tutorial-sample-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[![MATTR](../docs/assets/mattr-logo-square.svg)](https://github.com/mattrglobal)

# Android Remote Mobile Verifier Tutorial Sample App

A Jetpack Compose sample app that uses MATTR's `MobileCredentialVerifier` SDK to verify a mobile document (mDoc, for example a mobile driver's license) presented from a wallet app on the same device. The exchange follows OID4VP and ISO/IEC 18013-7 Annex B (the "remote" same-device presentation workflow).

This project accompanies the [Remote mobile verifiers tutorial](https://learn.mattr.global/docs/verification/remote-mobile-verifiers/tutorial).

## What the app does

1. Shows a **Request credentials** button that builds a presentation request for an mDL (`org.iso.18013.5.1.mDL`) and asks for the `family_name`, `given_name`, and `birth_date` claims.
2. Hands off to a compliant wallet app on the device, where the holder consents to share the requested claims.
3. Receives the verification result back via the OID4VP deep link declared in `AndroidManifest.xml` and renders the verified claims, the overall verification status, and any claim errors.

## Prerequisites

- Android Studio with a Kotlin DSL project setup.
- A physical Android device with API 24 or higher and internet access (the wallet redirect flow needs a real device).
- The MATTR `mobile-credential-verifier-<version>.zip` SDK distribution. This is distributed by MATTR and is **not** included in this repository.
- A configured MATTR VII tenant with a verifier application configuration (type `android`), a supported wallet configuration, and a trusted issuer. These are configured out of band; see the tutorial for details.

## Setup

1. **Add the SDK to the local repo.** Unzip `mobile-credential-verifier-<version>.zip`, then copy its `global` folder into the project's [repo/](repo/) directory at the root. Gradle resolves the `global.mattr.mobilecredential:verifier` dependency from there.

2. **Set your application id.** Open [app/build.gradle.kts](app/build.gradle.kts) and replace the placeholder `applicationId`:

```kotlin
applicationId = "com.example.mobileverifiertutorial"
```

The deep link scheme in [AndroidManifest.xml](app/src/main/AndroidManifest.xml) is bound to `${applicationId}`, so changing this value automatically updates the scheme that the wallet will redirect to.

3. **Point the app at your tenant.** Open [Constants.kt](app/src/main/java/com/example/mobileverifiertutorial/Constants.kt) and replace both placeholders:

```kotlin
object Constants {
const val TENANT_HOST = "https://your-tenant.vii.your-region.mattr.global"
const val APPLICATION_ID = "your-application-id"
}
```

- `TENANT_HOST` is the base URL of your MATTR VII tenant.
- `APPLICATION_ID` is the `id` of the verifier application configuration created in your tenant.

4. **Register your signing certificate with MATTR VII.** Get the SHA-256 thumbprint of your debug (or release) signing certificate:

```bash
./gradlew signingReport
```

Strip the colons and lowercase it, then update your tenant's verifier application configuration so that `packageName` matches step 2, `openid4vpConfiguration.redirectUri` uses `<applicationId>://oid4vp-callback`, and `packageSigningCertificateThumbprints` includes the thumbprint. This is done out of band against your MATTR VII tenant; see the tutorial for the exact request.

## Project structure

| Path | Purpose |
| --- | --- |
| [app/src/main/java/com/example/mobileverifiertutorial/Constants.kt](app/src/main/java/com/example/mobileverifiertutorial/Constants.kt) | Tenant host and application id (replace the placeholders). |
| [app/src/main/java/com/example/mobileverifiertutorial/MainActivity.kt](app/src/main/java/com/example/mobileverifiertutorial/MainActivity.kt) | SDK initialization, the credential request, and the view model that exposes results. |
| [app/src/main/java/com/example/mobileverifiertutorial/DocumentView.kt](app/src/main/java/com/example/mobileverifiertutorial/DocumentView.kt) | Composable that renders a verified document and its claims. |
| [app/src/main/AndroidManifest.xml](app/src/main/AndroidManifest.xml) | Declares the OID4VP callback intent filter bound to `${applicationId}`. |
| [settings.gradle.kts](settings.gradle.kts) | Adds the local `repo/` directory as a maven repository for the SDK. |
| [repo/](repo/) | Drop the unzipped SDK `global` folder here. Empty until you do. |

## Notes

- The placeholder values let the project sync and the app launch, but a credential request only succeeds once `Constants` points at a real tenant and application configuration, the signing thumbprint is registered with that tenant, and a compatible wallet is installed on the device.
- Trusted issuer certificates for the remote workflow are managed in your MATTR VII tenant, not in the app.

---

<p align="center"><a href="https://mattr.global" target="_blank"><img height="40px" src="../docs/assets/mattr-logo-tm.svg"></a></p><p align="center">Copyright © MATTR Limited. <a href="../LICENSE">Some rights reserved.</a><br/>“MATTR” is a trademark of MATTR Limited, registered in New Zealand and other countries.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}

android {
namespace = "com.example.mobileverifiertutorial"
compileSdk = 36

defaultConfig {
// Replace this with a package name you own before running on a real
// tenant. Whatever value you choose is also injected as the deep link
// scheme via ${applicationId} in AndroidManifest.xml, and must match
// the redirect URI configured for your verifier application in MATTR VII.
applicationId = "com.example.mobileverifiertutorial"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
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("androidx.navigation:navigation-compose:2.9.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RemoteVerifierTutorial">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.RemoteVerifierTutorial">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!--
OID4VP callback handler from the MobileCredentialVerifier SDK.
The scheme is bound to ${applicationId}, so the deep link automatically
matches whatever package name you set in app/build.gradle.kts. The
redirect URI registered for your verifier application in your MATTR VII
tenant must use this same scheme, e.g. ${applicationId}://oid4vp-callback
-->
<activity
android:name="global.mattr.mobilecredential.verifier.a2apresentation.callback.Openid4VpCallbackActivity"
android:exported="true"
tools:node="merge">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="${applicationId}"
android:host="oid4vp-callback" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.mobileverifiertutorial

// MARK: - Constants
// These values are specific to your own MATTR VII tenant and to the verifier
// application configuration you create within it (handled out of band, see the README).
// Replace both placeholders before running a real verification:
// - TENANT_HOST: the base URL of your MATTR VII tenant.
// - APPLICATION_ID: the `id` returned when you create the presentation application
// configuration in your tenant.
// The app will launch with these placeholder values, but a credential request will only
// succeed once they point at a real tenant and application configuration.
object Constants {
const val TENANT_HOST = "https://your-tenant.vii.your-region.mattr.global"
const val APPLICATION_ID = "your-application-id"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.example.mobileverifiertutorial

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import global.mattr.mobilecredential.verifier.dto.MobileCredentialPresentation

// MARK: - Section: Display Results (Step 4)
// Renders a single received mDoc as a Material card showing:
// - The document type (e.g. org.iso.18013.5.1.mDL)
// - The overall verification status (Verified / Invalid), colored
// - The flat list of claims that were returned
@Composable
fun DocumentView(document: MobileCredentialPresentation, modifier: Modifier = Modifier) {
val verified: Boolean = document.verificationResult.verified
val statusText: String = if (verified) "Verified" else "Invalid"
val statusColor: Color = if (verified) Color.Green else Color.Red
val flatClaims: List<String> = document.claims?.flatMap { (_, claimsMap) ->
claimsMap.map { (claim, value) -> "$claim: ${value.value}" }
} ?: emptyList()

Card(
modifier = modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = document.docType,
color = Color.Black,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = statusText,
color = statusColor,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
Spacer(modifier = Modifier.height(12.dp))
if (flatClaims.isEmpty()) {
Text(
text = "No claims",
color = Color.Black,
style = MaterialTheme.typography.labelMedium,
)
} else {
flatClaims.forEach { line ->
Text(
text = line,
color = Color.Black,
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
}
Loading
Loading