Flutter Integration Guide¶
Overview¶
BiometridFull is a comprehensive identity verification SDK that provides a complete. This guide explains how to integrate BiometridFull into a Flutter application using a custom plugin with platform channels.
Since BiometridFull is distributed as a native Android AAR (via Maven) and an iOS XCFramework (via CocoaPods), integration in Flutter requires a Flutter Plugin that bridges the native SDKs to Dart through MethodChannel and EventChannel.
Prerequisites¶
| Platform | Requirement |
|---|---|
| Flutter | 3.10.0+ |
| Dart | 3.0+ |
| Android | API Level 26+ (Android 8.0), Kotlin 2.2.0+ |
| iOS | 16.0+, Xcode 15+, Swift 5.9+ |
Step 1: Create the Flutter Plugin¶
Generate the plugin scaffold:
flutter create --org com.biometrid --template=plugin \
--platforms=android,ios -a kotlin -i swift \
biometrid_full_plugin
This creates the following structure:
biometrid_full_plugin/
├── lib/
│ ├── biometrid_full_plugin.dart
│ ├── biometrid_full_plugin_method_channel.dart
│ └── biometrid_full_plugin_platform_interface.dart
├── android/
│ ├── build.gradle
│ └── src/main/kotlin/.../BiometridFullPlugin.kt
├── ios/
│ ├── biometrid_full_plugin.podspec
│ └── Classes/BiometridFullPlugin.swift
├── example/
└── pubspec.yaml
Step 2: Configure Android Native Side¶
2.1 Add Repositories and Dependencies¶
Starting with 3.3.0, BiometridFull is distributed as a modular Android library. The main AAR contains the SDK code with native step dependencies declared as compileOnly. To use a step, declare its bridge alongside the main dependency — the bridge is a POM-only artifact that pulls in the right native libraries for that step.
You only need to add the repositories required by the bridges you actually use. If you never use NFC, you don't need the Regula repo. If you never use Liveness, you don't need the face01/iProov repos.
Edit android/build.gradle:
group 'com.biometrid.biometrid_full_plugin'
version '1.0'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
// BiometridFull main artifact + all bridges
maven { url "https://dl.cloudsmith.io/public/biometrid/mobile/maven/" }
// Liveness bridge repositories
maven { url "https://dl.cloudsmith.io/public/biometrid/face01-liveness/maven/" }
maven { url "https://raw.githubusercontent.com/iProov/android/master/maven/" }
// NFC bridge repository
maven { url "https://maven.regulaforensics.com/RegulaDocumentReader/" }
// AutoCapture / VideoConference / VideoLiveness bridges need no extra repos — mavenCentral is enough
}
}
android {
namespace 'com.biometrid.biometrid_full_plugin'
compileSdk 35
defaultConfig {
minSdk 26
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
}
dependencies {
// BiometridFull main SDK
implementation "com.biometrid:biometridfull:3.3.0"
// Bridges — declare one per step your flow uses. Bridges are POM-only and
// pull in the native libraries for each step. Drop the ones you don't need.
implementation "com.biometrid:biometridfull-liveness:3.3.0"
implementation "com.biometrid:biometridfull-nfc:3."
implementation "com.biometrid:biometridfull-autocapture:3.3.0"
implementation "com.biometrid:biometridfull-videoconference:3.3.0"
implementation "com.biometrid:biometridfull-videoliveness:3.3.0"
}
2.2 Add Required Permissions¶
Create or edit android/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.biometrid.biometrid_full_plugin">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />
</manifest>
2.3 Implement the Android Plugin¶
Edit android/src/main/kotlin/com/biometrid/biometrid_full_plugin/BiometridFullPlugin.kt:
package com.biometrid.biometrid_full_plugin
import android.app.Activity
import com.biometrid.biometridfull.BiometridFull
import com.biometrid.biometridfull.callbacks.BiometridFullCallback
import com.biometrid.biometridfull.helpers.ScreenType
import com.biometrid.biometridstandard.enums.Language
import com.biometrid.biometridstandard.model.BiometridErrorInfo
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class BiometridFullPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
EventChannel.StreamHandler {
private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel
private var eventSink: EventChannel.EventSink? = null
private var activity: Activity? = null
private var biometridFull: BiometridFull? = null
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
// ─── FlutterPlugin ─────────────────────────────────────────────
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel = MethodChannel(
binding.binaryMessenger,
"com.biometrid/biometrid_full"
)
methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(
binding.binaryMessenger,
"com.biometrid/biometrid_full_events"
)
eventChannel.setStreamHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
}
// ─── ActivityAware ──────────────────────────────────────────────
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
// ─── EventChannel.StreamHandler ─────────────────────────────────
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
// ─── MethodCallHandler ──────────────────────────────────────────
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"initialize" -> handleInitialize(call, result)
"start" -> handleStart(call, result)
"stop" -> handleStop(result)
else -> result.notImplemented()
}
}
// ─── Method Handlers ────────────────────────────────────────────
private fun handleInitialize(call: MethodCall, result: Result) {
val url = call.argument<String>("url")
val app = call.argument<String>("app")
val appUrl = call.argument<String>("appUrl")
val credential = call.argument<String>("credential")
val languageCode = call.argument<String>("language") ?: "en-GB"
val customHeaders = call.argument<Map<String, Any>>("customHeaders")
if (url == null || app == null || appUrl == null || credential == null) {
result.error(
"INVALID_ARGS",
"Missing required arguments: url, app, appUrl, credential",
null
)
return
}
val language = Language.entries.find { it.value == languageCode }
?: Language.English
val callback = createCallback(url, app, appUrl, credential, language, customHeaders)
biometridFull = BiometridFull(callback)
scope.launch {
try {
biometridFull?.initialize()
result.success(true)
} catch (e: Exception) {
result.error("INIT_ERROR", e.message, null)
}
}
}
private fun handleStart(call: MethodCall, result: Result) {
val processId = call.argument<String>("processId")
val currentActivity = activity
if (currentActivity == null) {
result.error("NO_ACTIVITY", "Plugin is not attached to an Activity", null)
return
}
if (biometridFull == null) {
result.error(
"NOT_INITIALIZED",
"BiometridFull is not initialized. Call initialize() first.",
null
)
return
}
scope.launch {
try {
biometridFull?.start(
processId = processId,
screenType = ScreenType(currentActivity)
)
result.success(true)
} catch (e: Exception) {
result.error("START_ERROR", e.message, null)
}
}
}
private fun handleStop(result: Result) {
biometridFull?.stop()
result.success(true)
}
// ─── Callback ───────────────────────────────────────────────────
private fun createCallback(
url: String,
app: String,
appUrl: String,
credential: String,
language: Language,
customHeaders: Map<String, Any>?
): BiometridFullCallback {
return object : BiometridFullCallback {
override val url: String = url
override val app: String = app
override val appUrl: String = appUrl
override val credential: String = credential
override val language: Language = language
override val customHeaders: Map<Any?, *>? = customHeaders
override fun initialized(status: Boolean, error: BiometridErrorInfo?) {
sendEvent(mapOf(
"type" to "initialized",
"status" to status,
"error" to error?.toMap()
))
}
override fun processCreated(processId: String) {
sendEvent(mapOf(
"type" to "processCreated",
"processId" to processId
))
}
override fun processUpdated(
processId: String,
stepId: String,
action: String
) {
sendEvent(mapOf(
"type" to "processUpdated",
"processId" to processId,
"stepId" to stepId,
"action" to action
))
}
override fun processFinished(processId: String) {
sendEvent(mapOf(
"type" to "processFinished",
"processId" to processId
))
}
override fun error(status: Boolean, error: BiometridErrorInfo) {
sendEvent(mapOf(
"type" to "error",
"status" to status,
"error" to error.toMap()
))
}
}
}
private fun sendEvent(data: Map<String, Any?>) {
activity?.runOnUiThread {
eventSink?.success(data)
}
}
private fun BiometridErrorInfo.toMap(): Map<String, Any?> {
return mapOf(
"code" to code,
"message" to message
)
}
}
2.4 Register Required Activities (Consumer App)¶
The consumer Flutter app must add these activities to its android/app/src/main/AndroidManifest.xml inside the <application> tag:
<activity
android:name="com.biometrid.biometridfull.ui.BiometridFullComposeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="false" />
<activity
android:name="com.biometrid.autocapture.ui.AutoCaptureActivity"
android:exported="false" />
<activity
android:name="com.biometrid.biometridfull.webview.AndroidWebViewActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="false" />
<activity
android:name="com.biometrid.biometridstandardnfc.activity.BiometridNFCActivity"
android:exported="false" />
Step 3: Configure iOS Native Side¶
3.1 Configure the Podspec¶
Edit ios/biometrid_full_plugin.podspec:
Pod::Spec.new do |s|
s.name = 'biometrid_full_plugin'
s.version = '3.3.0'
s.summary = 'Flutter plugin for BiometridFull identity verification SDK'
s.description = 'Integrates BiometridFull KMM SDK into Flutter for iOS'
s.homepage = 'https://biometrid.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Biometrid' => 'dev@biometrid.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'BiometridFull', '~> 3.3.0'
s.platform = :ios, '16.0'
s.ios.deployment_target = '16.0'
s.swift_version = '5.9'
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES',
}
end
Note: On iOS,
BiometridFullis published with subspecs (Liveness,NFC,AutoCapture,VideoConference,VideoLiveness) that mirror the Android bridges. They exist as semantic markers — today they all transitively pull in the sameCore, which carries the XCFramework and every native step pod. DeclaringBiometridFullalone is enough.
3.2 Configure Consumer App Podfile¶
The consumer Flutter app's ios/Podfile needs the Biometrid spec sources:
platform :ios, '16.0'
# Required CocoaPods spec sources for BiometridFull dependencies
source 'https://cdn.cocoapods.org'
source 'https://dl.cloudsmith.io/public/biometrid/mobile/cocoapods/index.git'
source 'https://dl.cloudsmith.io/public/biometrid/face01-liveness/cocoapods/index.git'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(
File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__
)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist."
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}."
end
require File.expand_path(
File.join('packages', 'flutter_tools', 'bin', 'podhelper'),
flutter_root
)
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end
end
3.3 Implement the iOS Plugin¶
Edit ios/Classes/BiometridFullPlugin.swift:
import Flutter
import UIKit
import BiometridFull
public class BiometridFullPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
private var eventSink: FlutterEventSink?
private var biometridFull: BiometridFull?
private var callbackHandler: BiometridCallbackHandler?
// ─── Plugin Registration ────────────────────────────────────────
public static func register(with registrar: FlutterPluginRegistrar) {
let methodChannel = FlutterMethodChannel(
name: "com.biometrid/biometrid_full",
binaryMessenger: registrar.messenger()
)
let eventChannel = FlutterEventChannel(
name: "com.biometrid/biometrid_full_events",
binaryMessenger: registrar.messenger()
)
let instance = BiometridFullPlugin()
registrar.addMethodCallDelegate(instance, channel: methodChannel)
eventChannel.setStreamHandler(instance)
}
// ─── FlutterStreamHandler ───────────────────────────────────────
public func onListen(
withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink
) -> FlutterError? {
self.eventSink = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}
// ─── Method Handling ────────────────────────────────────────────
public func handle(
_ call: FlutterMethodCall,
result: @escaping FlutterResult
) {
switch call.method {
case "initialize":
handleInitialize(call, result: result)
case "start":
handleStart(call, result: result)
case "stop":
handleStop(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
// ─── Initialize ─────────────────────────────────────────────────
private func handleInitialize(
_ call: FlutterMethodCall,
result: @escaping FlutterResult
) {
guard let args = call.arguments as? [String: Any],
let url = args["url"] as? String,
let app = args["app"] as? String,
let appUrl = args["appUrl"] as? String,
let credential = args["credential"] as? String else {
result(FlutterError(
code: "INVALID_ARGS",
message: "Missing required arguments: url, app, appUrl, credential",
details: nil
))
return
}
let languageCode = args["language"] as? String ?? "en-GB"
let customHeaders = args["customHeaders"] as? [AnyHashable: Any]
let language = mapLanguage(languageCode)
let callback = BiometridCallbackHandler(
url: url,
app: app,
appUrl: appUrl,
credential: credential,
language: language,
customHeaders: customHeaders,
eventSink: { [weak self] event in
DispatchQueue.main.async {
self?.eventSink?(event)
}
}
)
self.callbackHandler = callback
self.biometridFull = BiometridFull(callback: callback)
Task {
do {
try await self.biometridFull?.initialize()
DispatchQueue.main.async {
result(true)
}
} catch {
DispatchQueue.main.async {
result(FlutterError(
code: "INIT_ERROR",
message: error.localizedDescription,
details: nil
))
}
}
}
}
// ─── Start ──────────────────────────────────────────────────────
private func handleStart(
_ call: FlutterMethodCall,
result: @escaping FlutterResult
) {
let args = call.arguments as? [String: Any]
let processId = args?["processId"] as? String
guard biometridFull != nil else {
result(FlutterError(
code: "NOT_INITIALIZED",
message: "BiometridFull is not initialized. Call initialize() first.",
details: nil
))
return
}
guard let viewController = findTopMostViewController() else {
result(FlutterError(
code: "NO_VIEW_CONTROLLER",
message: "Could not find top view controller",
details: nil
))
return
}
Task {
do {
try await self.biometridFull?.start(
processId: processId,
screenType: ScreenType(controller: viewController)
)
DispatchQueue.main.async {
result(true)
}
} catch {
DispatchQueue.main.async {
result(FlutterError(
code: "START_ERROR",
message: error.localizedDescription,
details: nil
))
}
}
}
}
// ─── Stop ───────────────────────────────────────────────────────
private func handleStop(result: @escaping FlutterResult) {
biometridFull?.stop()
result(true)
}
// ─── Helpers ────────────────────────────────────────────────────
private func findTopMostViewController() -> UIViewController? {
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene }).first,
var topController = windowScene.windows
.first(where: { $0.isKeyWindow })?.rootViewController
else {
return nil
}
while let presented = topController.presentedViewController {
topController = presented
}
return topController
}
private func mapLanguage(_ code: String) -> SharedLanguage {
switch code {
case "pt-PT": return .portuguese
case "fr-FR": return .french
case "es-ES": return .spanish
case "it-IT": return .italian
default: return .english
}
}
}
// ─── Callback Handler ───────────────────────────────────────────────
class BiometridCallbackHandler: BiometridFullCallback {
var url: String
var app: String
var appUrl: String
var credential: String
var language: SharedLanguage
var customHeaders: [AnyHashable: Any]?
private let sendEvent: ([String: Any?]) -> Void
init(
url: String,
app: String,
appUrl: String,
credential: String,
language: SharedLanguage,
customHeaders: [AnyHashable: Any]?,
eventSink: @escaping ([String: Any?]) -> Void
) {
self.url = url
self.app = app
self.appUrl = appUrl
self.credential = credential
self.language = language
self.customHeaders = customHeaders
self.sendEvent = eventSink
}
func initialized(status: Bool, error: SharedBiometridErrorInfo?) {
sendEvent([
"type": "initialized",
"status": status,
"error": error.map { [
"code": $0.code as Any?,
"message": $0.message as Any?
] }
])
}
func processCreated(processId: String) {
sendEvent([
"type": "processCreated",
"processId": processId
])
}
func processUpdated(processId: String, stepId: String, action: String) {
sendEvent([
"type": "processUpdated",
"processId": processId,
"stepId": stepId,
"action": action
])
}
func processFinished(processId: String) {
sendEvent([
"type": "processFinished",
"processId": processId
])
}
func error(status: Bool, error: SharedBiometridErrorInfo) {
sendEvent([
"type": "error",
"status": status,
"error": [
"code": error.code as Any?,
"message": error.message as Any?
]
])
}
}
3.4 Add Required Info.plist Keys (Consumer App)¶
The consumer Flutter app must add these to ios/Runner/Info.plist:
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for identity verification</string>
<key>NFCReaderUsageDescription</key>
<string>NFC is required to read identity documents</string>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
Step 4: Dart API Layer¶
4.1 Platform Interface¶
Edit lib/biometrid_full_plugin_platform_interface.dart:
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'biometrid_full_plugin_method_channel.dart';
abstract class BiometridFullPluginPlatform extends PlatformInterface {
BiometridFullPluginPlatform() : super(token: _token);
static final Object _token = Object();
static BiometridFullPluginPlatform _instance =
MethodChannelBiometridFullPlugin();
static BiometridFullPluginPlatform get instance => _instance;
static set instance(BiometridFullPluginPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<bool> initialize({
required String url,
required String app,
required String appUrl,
required String credential,
String language = 'en-GB',
Map<String, dynamic>? customHeaders,
});
Future<bool> start({String? processId});
Future<bool> stop();
Stream<BiometridEvent> get onEvent;
}
4.2 Method Channel Implementation¶
Edit lib/biometrid_full_plugin_method_channel.dart:
import 'package:flutter/services.dart';
import 'biometrid_full_plugin_platform_interface.dart';
import 'biometrid_full_plugin.dart';
class MethodChannelBiometridFullPlugin
extends BiometridFullPluginPlatform {
static const MethodChannel _methodChannel =
MethodChannel('com.biometrid/biometrid_full');
static const EventChannel _eventChannel =
EventChannel('com.biometrid/biometrid_full_events');
Stream<BiometridEvent>? _eventStream;
@override
Future<bool> initialize({
required String url,
required String app,
required String appUrl,
required String credential,
String language = 'en-GB',
Map<String, dynamic>? customHeaders,
}) async {
final result = await _methodChannel.invokeMethod<bool>('initialize', {
'url': url,
'app': app,
'appUrl': appUrl,
'credential': credential,
'language': language,
if (customHeaders != null) 'customHeaders': customHeaders,
});
return result ?? false;
}
@override
Future<bool> start({String? processId}) async {
final result = await _methodChannel.invokeMethod<bool>('start', {
if (processId != null) 'processId': processId,
});
return result ?? false;
}
@override
Future<bool> stop() async {
final result = await _methodChannel.invokeMethod<bool>('stop');
return result ?? false;
}
@override
Stream<BiometridEvent> get onEvent {
_eventStream ??= _eventChannel
.receiveBroadcastStream()
.map((event) => BiometridEvent.fromMap(
Map<String, dynamic>.from(event as Map)));
return _eventStream!;
}
}
4.3 Public API and Models¶
Edit lib/biometrid_full_plugin.dart:
import 'biometrid_full_plugin_platform_interface.dart';
export 'biometrid_full_plugin.dart'
show BiometridFullPlugin, BiometridEvent, BiometridErrorInfo;
/// Flutter plugin for BiometridFull identity verification SDK.
///
/// Provides biometric verification capabilities including Liveness,
/// NFC, AutoCapture, OTP, Video Conference, and Video Liveness.
class BiometridFullPlugin {
/// Initializes the BiometridFull SDK.
///
/// Must be called before [start]. Connects to the Biometrid backend
/// and prepares all required services.
///
/// Parameters:
/// - [url]: The Biometrid API base URL (e.g., `https://api.biometrid.com/`)
/// - [app]: Your application identifier provided by Biometrid
/// - [appUrl]: The Biometrid web app URL (e.g., `https://app.biometrid.com/`)
/// - [credential]: Your credential key provided by Biometrid
/// - [language]: Language code for the verification interface (default: `en-GB`)
/// Supported: `en-GB`, `pt-PT`, `fr-FR`, `es-ES`, `it-IT`
/// - [customHeaders]: Optional custom HTTP headers for API requests
static Future<bool> initialize({
required String url,
required String app,
required String appUrl,
required String credential,
String language = 'en-GB',
Map<String, dynamic>? customHeaders,
}) {
return BiometridFullPluginPlatform.instance.initialize(
url: url,
app: app,
appUrl: appUrl,
credential: credential,
language: language,
customHeaders: customHeaders,
);
}
/// Starts the identity verification process.
///
/// Presents the WebView-based verification interface with native
/// biometric capabilities.
///
/// Parameters:
/// - [processId]: Optional process ID. Pass `null` to create a new
/// process, or provide an existing ID to resume a previous process.
static Future<bool> start({String? processId}) {
return BiometridFullPluginPlatform.instance.start(processId: processId);
}
/// Stops the current verification process and cleans up resources.
///
/// Cancels any active WebView sessions and pending native operations.
/// Safe to call at any point.
static Future<bool> stop() {
return BiometridFullPluginPlatform.instance.stop();
}
/// Stream of SDK lifecycle events.
///
/// Emits events for initialization, process creation, step updates,
/// process completion, and errors.
static Stream<BiometridEvent> get onEvent {
return BiometridFullPluginPlatform.instance.onEvent;
}
}
/// Represents an event emitted by the BiometridFull SDK.
class BiometridEvent {
/// The event type: `initialized`, `processCreated`, `processUpdated`,
/// `processFinished`, or `error`.
final String type;
/// Process ID (available for process-related events).
final String? processId;
/// Step ID (available for `processUpdated` events).
final String? stepId;
/// Action type (available for `processUpdated` events).
final String? action;
/// Initialization/error status.
final bool? status;
/// Error information (available for `initialized` and `error` events).
final BiometridErrorInfo? error;
BiometridEvent({
required this.type,
this.processId,
this.stepId,
this.action,
this.status,
this.error,
});
factory BiometridEvent.fromMap(Map<String, dynamic> map) {
return BiometridEvent(
type: map['type'] as String,
processId: map['processId'] as String?,
stepId: map['stepId'] as String?,
action: map['action'] as String?,
status: map['status'] as bool?,
error: map['error'] != null
? BiometridErrorInfo.fromMap(
Map<String, dynamic>.from(map['error'] as Map))
: null,
);
}
@override
String toString() =>
'BiometridEvent(type: $type, processId: $processId, '
'stepId: $stepId, action: $action, status: $status, error: $error)';
}
/// Error information from the BiometridFull SDK.
class BiometridErrorInfo {
/// Error code identifier (e.g., `"MBF001"`).
/// Codes follow the prefix convention: `MBF` = BiometridFull module errors.
final String? code;
/// Human-readable error description.
final String? message;
BiometridErrorInfo({this.code, this.message});
factory BiometridErrorInfo.fromMap(Map<String, dynamic> map) {
return BiometridErrorInfo(
code: map['code'] as String?,
message: map['message'] as String?,
);
}
@override
String toString() => 'BiometridErrorInfo(code: $code, message: $message)';
}
Step 5: Usage in a Flutter App¶
5.1 Add the Plugin Dependency¶
In your Flutter app's pubspec.yaml:
dependencies:
flutter:
sdk: flutter
biometrid_full_plugin:
path: ../biometrid_full_plugin # Or a git/pub reference
5.2 Complete Example¶
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:biometrid_full_plugin/biometrid_full_plugin.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Biometrid Verification',
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const VerificationScreen(),
);
}
}
class VerificationScreen extends StatefulWidget {
const VerificationScreen({super.key});
@override
State<VerificationScreen> createState() => _VerificationScreenState();
}
class _VerificationScreenState extends State<VerificationScreen> {
bool _isInitialized = false;
bool _isLoading = false;
bool _isProcessCompleted = false;
String? _processId;
String? _errorMessage;
StreamSubscription<BiometridEvent>? _eventSubscription;
@override
void initState() {
super.initState();
_listenToEvents();
}
@override
void dispose() {
_eventSubscription?.cancel();
BiometridFullPlugin.stop();
super.dispose();
}
void _listenToEvents() {
_eventSubscription = BiometridFullPlugin.onEvent.listen((event) {
setState(() {
switch (event.type) {
case 'initialized':
_isInitialized = event.status ?? false;
if (event.error != null) {
_errorMessage = event.error!.message;
}
break;
case 'processCreated':
_processId = event.processId;
break;
case 'processUpdated':
// Step completed: event.stepId, event.action
debugPrint(
'Step updated: ${event.stepId} - ${event.action}',
);
break;
case 'processFinished':
_isProcessCompleted = true;
_isLoading = false;
break;
case 'error':
_errorMessage = event.error?.message ?? 'Unknown error';
_isLoading = false;
break;
}
});
});
}
Future<void> _initialize() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
await BiometridFullPlugin.initialize(
url: 'https://api.biometrid.com/',
app: 'your-app-id',
appUrl: 'https://app.biometrid.com/',
credential: 'your-credential',
language: 'en-GB',
);
} catch (e) {
setState(() {
_errorMessage = 'Initialization failed: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _startVerification() async {
setState(() {
_isLoading = true;
_errorMessage = null;
_isProcessCompleted = false;
});
try {
await BiometridFullPlugin.start();
} catch (e) {
setState(() {
_errorMessage = 'Start failed: $e';
_isLoading = false;
});
}
}
Future<void> _stop() async {
await BiometridFullPlugin.stop();
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Biometrid Verification')),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Status Card
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'SDK Status',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
_StatusRow(
label: 'Initialized',
value: _isInitialized,
),
if (_processId != null)
Text('Process: $_processId'),
if (_isProcessCompleted)
const Text(
'Verification Complete!',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
const SizedBox(height: 16),
// Error Banner
if (_errorMessage != null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_errorMessage!,
style: TextStyle(color: Colors.red.shade700),
),
),
const SizedBox(height: 16),
// Loading Indicator
if (_isLoading) const Center(child: CircularProgressIndicator()),
const Spacer(),
// Action Buttons
ElevatedButton(
onPressed: (!_isInitialized && !_isLoading)
? _initialize
: null,
child: const Text('Initialize SDK'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: (_isInitialized && !_isLoading)
? _startVerification
: null,
child: const Text('Start Verification'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: _isLoading ? _stop : null,
child: const Text('Stop'),
),
],
),
),
);
}
}
class _StatusRow extends StatelessWidget {
final String label;
final bool value;
const _StatusRow({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(
value ? Icons.check_circle : Icons.radio_button_unchecked,
color: value ? Colors.green : Colors.grey,
size: 18,
),
const SizedBox(width: 8),
Text(label),
],
);
}
}
Step 6: Consumer App Build Configuration¶
Android build.gradle (app-level)¶
Ensure the consumer app has the required repositories in android/build.gradle:
allprojects {
repositories {
google()
mavenCentral()
// BiometridFull main artifact + all bridges
maven { url "https://dl.cloudsmith.io/public/biometrid/mobile/maven/" }
// Liveness bridge
maven { url "https://dl.cloudsmith.io/public/biometrid/face01-liveness/maven/" }
maven { url "https://raw.githubusercontent.com/iProov/android/master/maven/" }
// NFC bridge
maven { url "https://maven.regulaforensics.com/RegulaDocumentReader/" }
}
}
Only add the bridge repositories for the steps you actually use. AutoCapture, VideoConference, and VideoLiveness bridges resolve from
mavenCentraland need no extra repo.
Set minSdkVersion in android/app/build.gradle:
android {
defaultConfig {
minSdk 26
}
}
API Reference Summary¶
Dart API¶
| Method | Description |
|---|---|
BiometridFullPlugin.initialize(...) |
Initializes SDK with credentials. Must be called first. |
BiometridFullPlugin.start({processId}) |
Starts verification. Pass null for new process or an ID to resume. |
BiometridFullPlugin.stop() |
Stops the current process and cleans up resources. |
BiometridFullPlugin.onEvent |
Stream of BiometridEvent for lifecycle callbacks. |
Event Types¶
| Event Type | Fields | Description |
|---|---|---|
initialized |
status, error? |
SDK initialization completed |
processCreated |
processId |
New verification process created |
processUpdated |
processId, stepId, action |
A step was completed |
processFinished |
processId |
Verification process completed |
error |
status, error |
An error occurred |
Configuration Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
String |
Yes | Biometrid API base URL |
app |
String |
Yes | Application identifier |
appUrl |
String |
Yes | Biometrid web application URL |
credential |
String |
Yes | Credential key |
language |
String |
No | Language code (default: en-GB) |
customHeaders |
Map<String, dynamic>? |
No | Custom HTTP headers |
Supported Languages¶
| Code | Language |
|---|---|
en-GB |
English |
pt-PT |
Portuguese |
fr-FR |
French |
es-ES |
Spanish |
it-IT |
Italian |
Troubleshooting¶
Android¶
| Issue | Solution |
|---|---|
Duplicate class errors |
Add packagingOptions { exclude 'META-INF/*.kotlin_module' } to your app's build.gradle |
minSdk version conflict |
Ensure minSdk 26 in your app's build.gradle |
| Missing repositories | Verify all Maven repository URLs are added to the project-level build.gradle |
| Activity not found | Ensure required activities are declared in AndroidManifest.xml |
| ProGuard issues | Add -keep class com.biometrid.** { *; } to your ProGuard rules |
iOS¶
| Issue | Solution |
|---|---|
| Pod install fails | Ensure all source URLs are added to Podfile |
| Deployment target mismatch | Set IPHONEOS_DEPLOYMENT_TARGET = '16.0' in post_install |
| Missing permissions | Verify Info.plist keys for Camera, Microphone, and NFC |
| Framework not found | Run pod install --repo-update to refresh pod specs |
| Build library for distribution | Add BUILD_LIBRARY_FOR_DISTRIBUTION = 'YES' in post_install |
| NFC entitlement missing | Enable "Near Field Communication Tag Reading" capability in Xcode |
General¶
| Issue | Solution |
|---|---|
| Events not received | Ensure onEvent stream is subscribed before calling initialize() |
NOT_INITIALIZED error |
Call initialize() and wait for completion before calling start() |
| Process not resuming | Pass the exact processId received from processCreated event |
ProGuard Rules (Android)¶
If your app uses ProGuard/R8, add these rules to android/app/proguard-rules.pro:
# BiometridFull SDK
-keep class com.biometrid.** { *; }
-keep interface com.biometrid.** { *; }
# Kotlin Serialization
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt
# Kotlin Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
Version Compatibility¶
| Component | Version |
|---|---|
| BiometridFull Android (Maven) | com.biometrid:biometridfull:3.3.0 |
| BiometridFull Android bridges | com.biometrid:biometridfull-{liveness,nfc,autocapture,videoconference,videoliveness}:3.3.0 |
| BiometridFull iOS (CocoaPods) | BiometridFull ~> 3.3.0 |
| iOS Deployment Target | 16.0 |
| Android Min SDK | 26 |
| Android Compile SDK | 35 |
| Kotlin | 2.2.0+ |
| Swift | 5.9+ |