readid_flutter 4.115.2
readid_flutter: ^4.115.2 copied to clipboard
A Flutter plugin for integrating ReadID's native iOS and Android SDKs. This plugin wraps ReadID's native SDKs.
example/lib/main.dart
/*
* ReadID Flutter SDK Sample Code
*
* Copyright © 2024 Inverid B.V. All rights reserved.
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:iproov_flutter/iproov_flutter.dart';
import 'package:readid_example/document_selection.dart';
import 'package:readid_example/strings.dart';
import 'package:readid_example/ui/dropdown_widget.dart';
import 'package:readid_example/ui/generic_dialog.dart';
import 'package:readid_example/ui/progress_widget.dart';
import 'package:readid_flutter/readid.dart';
import 'handle_response.dart';
void main() async {
runApp(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple, brightness: Brightness.light),
fontFamily: Strings.roboto,
useMaterial3: true),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple, brightness: Brightness.dark),
fontFamily: Strings.roboto,
useMaterial3: true),
themeMode: ThemeMode.system,
home: const ReadIDApp(),
),
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
}
class ReadIDApp extends StatefulWidget {
const ReadIDApp({super.key});
@override
State<ReadIDApp> createState() => _ReadIDAppState();
}
class _ReadIDAppState extends State<ReadIDApp> {
/// The possible flows the user can choose in this example app.
final List<ReadIDFlow> _flows = [
NFCWithAccessControlFlow(),
VIZOnlyOnePageFlow()
];
/// The text that is displayed when the flow is finished.
String? _statusText;
/// The document selection the user selected from the dropdown menu depending on the flow
DocumentSelection _documentSelection =
DocumentTypeSelection(DocumentType.passport);
/// The ReadID plugin, you use this to start a ReadID flow and verify identity documents.
final _readidPlugin = ReadID();
/// The Visual Inspection Zone result. This will be available after a ReadID Session is complete.
VIZResult? _vizResult;
/// The Near Field Communication result. This will be available after an NFC chip read.
NFCResult? _nfcResult;
/// When an only VIZ Flow is chosen, it's possible to chain the result to scan the NFC chip.
bool _shouldShowFlowChainingButton = false;
/// After a NFCOnlyFlow (i.e. last flowchaining step), we show the option to perform face verification with iProov.
bool _shouldShowStartiProovButton = false;
/// After a flow with no NFC chip face image, we show the option to perform optical verification as fallback.
bool _shouldShowOpticalVerificationFallbackButton = false;
/// Will be set when Veriff returned `resubmission_required` which indicates that a new VIZ capture should be submitted under the same Veriff session.
String? _submissionId;
/// Will be populated with the face image on the identity document, if available.
Uint8List? _image;
/// Will be set when iProov or Veriff are running
ProgressState? progressState;
// Put your API credentials in a json file and pass them using --dart-define-from-file
final String _baseUrl = const String.fromEnvironment('BASE_URL');
// Note: You should have different access keys for each server environment
final String _accessKey = const String.fromEnvironment('ACCESS_KEY');
@override
void initState() {
super.initState();
resumeReadID();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: progressState == null
? ListView(children: [
DropdownWidget(
flows: _flows,
documentSelection: _documentSelection,
onSubmit: (ReadIDFlow selectedFlow,
DocumentSelection documentSelection) {
_documentSelection = documentSelection;
_startReadID(selectedFlow);
},
onFlowChain: () {
_continueFlowChain();
},
onIProov: () {
_startiProov();
},
onVeriff: () {
_verifyVeriff();
},
onOnfido: () {
_verifyOnfido();
},
statusText: _statusText,
shouldShowFlowChain: _shouldShowFlowChainingButton,
shouldShowIProov: _shouldShowStartiProovButton,
shouldShowOpticalVerification:
_shouldShowOpticalVerificationFallbackButton,
image: _image,
),
])
: ProgressWidget(state: progressState!));
}
// Method to start the ReadID process
// It takes two parameters: flow and session
// flow is the ReadIDFlow object that will be used in the ReadID process
// session is the ReadIDSession object that will be used in the ReadID process in case of flow chaining
void _startReadID(ReadIDFlow flow, [ReadIDSession? session]) async {
// As we allow flow chain and facial orchestration passively (i.e. the user needs to trigger it)
// we don't know exactly when to release the session unless the user finished with iProov (already handled).
// Releasing the session when there is no current active session (i.e. flow chain) means that we can release the previous one
if (session == null) {
releaseSession();
}
ReadIDResult? readIDResult;
try {
// If flow is an instance of NFCWithAccessControlFlow,
// configure it with _configureNFCWithAccessControlFlow method
if (flow is NFCWithAccessControlFlow) {
_configureNFCWithAccessControlFlow(flow);
}
// If flow is an instance of VIZOnlyOnePageFlow,
// configure it with _configureVIZOnlyOnePageFlow method
if (flow is VIZOnlyOnePageFlow) {
_configureVIZOnlyOnePageFlow(flow);
}
// Start the ReadID process with the specified flow
// The result of the ReadID process will be stored in readIDResult
readIDResult = await _readidPlugin.startReadID(flow: flow);
} on Failure catch (e) {
handleFailure(e);
} on PlatformException catch (e) {
handleErrorResponse(e);
} finally {
updateUI(readIDResult: readIDResult);
}
}
// Method to configure NFCWithAccessControlFlow
// It takes one parameter: flow, which is an instance of NFCWithAccessControlFlow
void _configureNFCWithAccessControlFlow(NFCWithAccessControlFlow flow) {
UIFont buttonFont = UIFont();
buttonFont.name = 'MarkerFelt-Thin';
buttonFont.size = 24;
UIResources uiResources = UIResources();
uiResources.customBundleIdentifier = 'com.readid.readidExample.flutter';
uiResources.buttonFont = buttonFont;
flow
..baseUrl = _baseUrl
..accessKey = _accessKey
// Indicates whether an NFC result screen will be shown.
..shouldShowNFCResult = true
// If enabled, tries to read images(face or signature)
// If no images are needed, set to false to speed up reading process
..shouldReadImages = true
// Shows a skip reading button after the specified number of attempts.
// To hide or never show the skip button, set it to -1.
// To show skip button immediately, set it to 0.
// Set to a value > 0, to show skip button after specified number of attempts.
..allowSkipReadingAfterAttempts = 2
// Add the type of documents to be processed here.
..allowedDocumentTypes = List.from({_documentSelection.value})
// Style your iOS app
..uiResources = uiResources;
}
/// Method to configure VIZOnlyOnePageFlow.
/// We assume that after this one page flow, we either go to the NFC flow if possible, or fall back to the optical flow.
void _configureVIZOnlyOnePageFlow(VIZOnlyOnePageFlow flow) {
flow
..baseUrl = _baseUrl
..accessKey = _accessKey
// Add the type of documents to be processed here.
// For a faster VIZ capture, it is recommended to keep a single document type, as it avoids the VIZ capture algorithm having to check for multiple document types.
..allowedPageTypes = List.from({_documentSelection.value})
// We intend to followup with an NFCOnlyFlow, so do not commit the session yet. Note that ReadIDSessions can only be committed once.
..preventSessionCommit = true
// Configures the preferred MRZ validation, the default is 'accessControl'.
..mrzValidation = MRZValidation.accessControl
// Configure this property to specify if the qr code detection is required. This can be configured to .required if the QR code detection is mandatory
..qrCodeFeatureRequirement = FeatureRequirement.notRequired
// If this is true, a document selection screen will be displayed.
// This significantly improve processing time as only the selected document type has to be processed.
..shouldShowDocumentSelection = false
// Set time interval here if it is required to show manual capture button after the specified time interval. Set it to 0 to show the button instantly.
// Setting this to -1 will never show the manual capture button.
..allowManualCaptureAfterTimeout = -1
// Indicates whether an screen with the VIZ result will be shown. Usually this is only used during development.
..shouldShowVIZResult = false
// If this is true, you can record the screen and take snapshots as it is, this is useful for debug builds
..shouldAllowScreenshots = true
// Use the following properties to control the quality of the resulting capture.
// You might want to disable these during development of your app for faster testing.
..shouldRequireNoGlareOnDocument = true
..shouldRequireSharpImage = true
..shouldRequireNoFingerOnDocument = true;
}
/// Continues the VIZ flow to the NFC flow.
void _continueFlowChain() async {
ReadIDSession? session = _vizResult?.readIDSession;
/// Retrieve the NFC access key used to access the identity document.
NFCAccessKey? key = _vizResult?.nfcAccessKey;
if (session == null || key == null) {
return;
}
NFCOnlyFlow nfcFlow = NFCOnlyFlow(key, _vizResult?.documentInfo);
nfcFlow
..baseUrl = _baseUrl
..accessKey = _accessKey
..readIDSession = session;
_startReadID(nfcFlow, session);
}
/// Start facial verification with iProov. This requires a separate subscription.
void _startiProov() async {
if (_nfcResult?.readIDSession == null) {
Fluttertoast.showToast(
msg: Strings.statusNoReadIDSession, timeInSecForIosWeb: 3);
return;
}
setState(() {
progressState = IndeterminateProgress();
});
// Retrieves an iProov VerifyToken for this ReadID session. The server must have iProov enabled.
// This requires a separate subscription. This method is only available in the client-server and hybrid versions of ReadID.
// If you call this method and your subscription is client-only you will get a runtime exception
final iProovResult = await _readidPlugin.retrieveIProovToken(
sessionId: _nfcResult!.readIDSession!.sessionId);
if (iProovResult.response == null) {
progressState = null;
Fluttertoast.showToast(
msg: Strings.statusiProovError, timeInSecForIosWeb: 3);
return;
}
if (iProovResult.response!.attemptsLeft == 0) {
Fluttertoast.showToast(
msg: Strings.statusiProovNoAttemptsLeft, timeInSecForIosWeb: 3);
progressState = null;
return;
}
final stream = IProov.launch(
streamingUrl: 'wss://eu.rp.secure.iproov.me/ws',
token: iProovResult.response!.verifyToken);
stream.listen((event) {
if (event is IProovEventConnecting) {
// The SDK is connecting to the server. You should provide an indeterminate progress indicator
// to let the user know that the connection is taking place.
// We are already showing an indeterminate progress indicator because IProovEventConnecting takes
// a while to trigger
} else if (event is IProovEventConnected) {
// The SDK has connected, and the iProov user interface will now be displayed. You should hide
// any progress indication at this point.
progressState = null;
} else if (event is IProovEventProcessing) {
// The SDK will update your app with the progress of streaming to the server and authenticating
// the user. This will be called multiple times as the progress updates.
progressState = DeterminateProgress(event.progress, event.message);
} else if (event is IProovEventSuccess) {
// The user was successfully verified/enrolled and the token has been validated.
// You can access the following properties:
//final image = event.frame;
progressState = null;
_validateiProovToken(token: iProovResult.response!.verifyToken);
} else if (event is IProovEventCanceled) {
// The user canceled iProov, either by pressing the close button at the top of the screen, or sending
// the app to the background. (event.canceler == Canceler.user)
// Or, the app canceled (event.canceler == Canceler.app) by canceling the subscription to the
// Stream returned from IProov.launch().
// You should use this to determine the next step in your flow.
progressState = null;
_validateiProovToken(token: iProovResult.response!.verifyToken);
} else if (event is IProovEventFailure) {
// The user was not successfully verified/enrolled, as their identity could not be verified,
// or there was another issue with their verification/enrollment. A reason (as a string)
// is provided as to why the claim failed, along with a feedback code from the back-end.
Fluttertoast.showToast(msg: event.reason, timeInSecForIosWeb: 3);
progressState = null;
_validateiProovToken(token: iProovResult.response!.verifyToken);
} else if (event is IProovEventError) {
// The user was not successfully verified/enrolled due to an error (e.g. lost internet connection).
// You will be provided with an Exception (see below).
// It will be called once, or never.
final error = event.error; // IProovException provided by the SDK
Fluttertoast.showToast(
msg: error.message ?? error.title, timeInSecForIosWeb: 3);
progressState = null;
_validateiProovToken(token: iProovResult.response!.verifyToken);
}
setState(() {});
});
}
/// Validates an iProov VerifyToken for this ReadID session. The server must have iProov enabled. This requires a
/// separate subscription. This method is only available in the client-server and hybrid versions of ReadID.
void _validateiProovToken({required String token}) async {
setState(() {
_shouldShowStartiProovButton = false;
});
// If you call this method and your subscription is client-only you will get a runtime exception
final validation = await _readidPlugin.validateIProovToken(
sessionId: _nfcResult!.readIDSession!.sessionId, token: token);
if (validation.passed) {
Fluttertoast.showToast(
msg: Strings.statusiProovSucceeded, timeInSecForIosWeb: 3);
} else {
Fluttertoast.showToast(
msg: Strings.statusiProovFailed, timeInSecForIosWeb: 3);
}
releaseSession();
}
/// Start optical verification with Veriff
void _verifyVeriff({int attempt = 0}) async {
final sessionId = _nfcResult?.readIDSession?.sessionId ??
_vizResult?.readIDSession?.sessionId;
if (sessionId == null) {
Fluttertoast.showToast(
msg: Strings.statusNoReadIDSession, timeInSecForIosWeb: 3);
return;
}
setState(() {
progressState = IndeterminateProgress();
_shouldShowOpticalVerificationFallbackButton = false;
});
try {
if (attempt == 0) {
// as we have VIZOnlyOnePageFlow configured with preventSessionCommit set to true because we keep it open for a
// possible flow chain, we need to commit the session first before calling verifyVeriff, otherwise we will get a
// 404 error
// If you call this method and your subscription is client-only you will get a runtime exception
await _readidPlugin.commitSession(sessionId: sessionId);
}
// we cannot longer perform a flow chain after the session has been committed
_shouldShowFlowChainingButton = false;
// Requests the server to check if the VIZ images that were passed to the third-party optical verification provider have finished processing.
// - note: The server must have Veriff enabled. This requires a separate subscription. This method is only available in the client-server and hybrid versions of ReadID. If you call this method and your subscription is client-only you will get a runtime exception
// - important: The app should stop polling if one of the following conditions are true:
// * If `veriffResponse` and `veriffResponse.verification` and `veriffResponse.verification.status` are available (not nil). In this case `veriffResponse.verification.status` will be one of "approved", "resubmission_requested", or “declined". The values “review", “expired", and "abandoned" are also verification status values defined in Veriff docs, but are not supported.
// * If `veriffResponse` is not null and `veriffResponse.status` reports an error (not null or "success", but "fail")
// * If `veriffResponse` is still null after ~30 seconds (polling timeout).
final veriffDecision = await _readidPlugin.verifyVeriff(
sessionId: sessionId, submissionId: _submissionId);
final status = veriffDecision.veriffResponse?.verification?.status;
if (status != null) {
progressState = null;
if (status == "resubmission_requested") {
Fluttertoast.showToast(
msg: Strings.veriffResubmissionRequested, timeInSecForIosWeb: 3);
_submissionId = veriffDecision.submissionId;
} else {
Fluttertoast.showToast(
msg: '${Strings.veriffDecision} $status', timeInSecForIosWeb: 3);
_submissionId = null;
}
} else if (veriffDecision.veriffResponse?.status == "fail") {
progressState = null;
Fluttertoast.showToast(
msg:
'${Strings.veriffDecision} ${veriffDecision.veriffResponse?.status}',
timeInSecForIosWeb: 3);
} else if (attempt > 5) {
// as stated in the docs, after 30" (5*6) we timeout
progressState = null;
Fluttertoast.showToast(
msg: Strings.veriffTimeout, timeInSecForIosWeb: 3);
} else {
Future.delayed(const Duration(milliseconds: 5000), () {
_verifyVeriff(attempt: attempt + 1);
}); // poll after 5 seconds
}
} on PlatformException catch (e) {
progressState = null;
handleErrorResponse(e);
} finally {
setState(() {});
}
}
/// Start optical verification with Onfido
void _verifyOnfido({int attempt = 0}) async {
final sessionId = _nfcResult?.readIDSession?.sessionId ??
_vizResult?.readIDSession?.sessionId;
if (sessionId == null) {
Fluttertoast.showToast(
msg: Strings.statusNoReadIDSession, timeInSecForIosWeb: 3);
return;
}
setState(() {
progressState = IndeterminateProgress();
_shouldShowOpticalVerificationFallbackButton = false;
});
try {
if (attempt == 0) {
// as we have VIZOnlyOnePageFlow configured with preventSessionCommit set to true because we keep it open for a
// possible flow chain, we need to commit the session first before calling verifyOnfido, otherwise we will get a
// 404 error
await _readidPlugin.commitSession(sessionId: sessionId);
}
// we cannot longer perform a flow chain after the session have been committed
_shouldShowFlowChainingButton = false;
// Requests the server to check if the VIZ images that were passed to the third-party optical verification provider have finished processing.
// - note: The server must have Onfido enabled. This requires a separate subscription. This method is only available in the client-server and hybrid versions of ReadID. If you call this method and your subscription is client-only you will get a runtime exception
final verifyOnfidoResponse =
await _readidPlugin.verifyOnfido(sessionId: sessionId);
if (verifyOnfidoResponse.state == ReadIDState.error) {
progressState = null;
Fluttertoast.showToast(
msg:
'${Strings.onfidoError} ${verifyOnfidoResponse.errorResponse?.message}',
timeInSecForIosWeb: 3);
} else if (verifyOnfidoResponse.state == ReadIDState.complete) {
progressState = null;
Fluttertoast.showToast(
msg: '${Strings.veriffDecision} $verifyOnfidoResponse.state',
timeInSecForIosWeb: 3);
} else {
// poll again after 10'
Future.delayed(const Duration(milliseconds: 10000), () {
_verifyOnfido(attempt: attempt + 1);
});
}
} on PlatformException catch (e) {
progressState = null;
handleErrorResponse(e);
} finally {
setState(() {});
}
}
/// Updates the UI to show information from the session, if there is any.
void updateUI({required ReadIDResult? readIDResult}) async {
if (readIDResult == null) {
_statusText = null;
_image = null;
setState(() {});
}
final vizResult = readIDResult?.vizResult;
final nfcResult = readIDResult?.nfcResult;
if (nfcResult != null) {
// this is how to retrieve the data from the result, please check the documentation
// for more details on where to find the data you are interested in
_statusText =
"${Strings.statusNFCResultReceived}\n ${nfcResult.readIDSession?.documentContent?.secondaryIdentifier} "
"${nfcResult.readIDSession?.documentContent?.primaryIdentifier}"
"\n${Strings.verificationResult} ${nfcResult.readIDSession?.consolidatedIdentityData?.chipCloneDetection.value}";
_image = nfcResult.faceImage;
_vizResult = null;
_nfcResult = nfcResult;
// We only want to enable iProov if we come from a flowchain
if (_shouldShowFlowChainingButton) {
_shouldShowStartiProovButton = true;
}
_shouldShowFlowChainingButton = false;
} else if (vizResult != null) {
// nullify any previous _nfcResult just in case
_nfcResult = null;
// keep a reference for potential flow chain
_vizResult = vizResult;
_shouldShowFlowChainingButton = true;
// Show optical verification buttons in case we only have VIZ face image (i.e. selected VIZOnlyOnePageFlow with Identity Card TD1 Front)
if (vizResult.frontCaptureResult?.faceImageFeature != null) {
_shouldShowOpticalVerificationFallbackButton = true;
}
_statusText = "${Strings.statusVIZResultReceived}\n"
"${Strings.documentCodeLabel} ${vizResult.documentInfo?.documentCode}\n"
"${Strings.issuingCountryLabel} ${vizResult.documentInfo?.issuingCountry}";
} else {
_statusText = Strings.statusNoResult;
}
setState(() {});
}
/// Always call [release] on the ReadIDSession when the session is complete to prevent memory leaks.
void releaseSession() {
_nfcResult?.readIDSession?.release();
}
void handleFailure(Failure failure) {
handleResponse(failure, context);
}
void handleErrorResponse(PlatformException e) {
showGenericDialog(
context: context,
title: Strings.genericErrorTitle,
message: e.message ?? Strings.genericErrorMessage);
}
/// Call resumeReadID to make sure that the user can continue the session should your Flutter activity be killed on Android.
void resumeReadID() async {
await Future.delayed(const Duration(seconds: 1));
ReadIDResult result = await _readidPlugin.resumeReadID();
if (result.vizResult == null) {
Fluttertoast.showToast(msg: "No previous VIZ result");
} else {
Fluttertoast.showToast(
msg:
"Welcome back ${result.vizResult?.readIDSession?.documentContent?.secondaryIdentifier}");
}
_image = result.nfcResult?.faceImage;
updateUI(readIDResult: result);
}
}