readid_flutter 4.115.2 copy "readid_flutter: ^4.115.2" to clipboard
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);
  }
}
5
likes
150
points
643
downloads

Publisher

verified publisherinverid.com

Weekly Downloads

A Flutter plugin for integrating ReadID's native iOS and Android SDKs. This plugin wraps ReadID's native SDKs.

Homepage

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on readid_flutter