Flutter Advanced Integration Scenarios

๐Ÿ“˜

Sample Project

You can find the sample project for a complete Flutter Integration in dynamic mode for both android and iOS.

If you require more control over the journey and order of the steps or show custom screens between ID verification steps, you can use IDWise SDK in dynamic mode. This mode allows you to start specific steps and for you application to regain control after each step. Please note this integration mode requires more code and responsibility on your side.

Starting a new journey in dynamic mode

First ensure you have added the SDKs (Android, and iOS) dependencies and initialize the SDK as in Step 1, Step 2 and Step 3.

Once the SDK is initialized, it becomes ready to start an ID verification journey flow. To commence the flow, the SDK offers a startDynamicJourney method, as demonstrated below.

Future<void> startDynamicJourney() async {
  try {
    final startJourneyArgs = {
      "journeyDefinitionId": "<JOURNEY_DEFINITION_ID>",
      "referenceNo": "<REFERENCE_NUMBER>"
      "locale": "en,
    };
    platformChannel.invokeMethod('startDynamicJourney', startJourneyArgs);

    setJourneyMethodHandler();

  } on PlatformException catch (e) {
    print("Failed : '${e.message}'.");
  }
}

This method takes the following parameters:

  • journeyDefinitionId: (also known as flowId) This is a unique identifier that identifies your journey flow. IDWise shares this with you when you register to use IDWise system.
  • referenceNo: (Optional) This parameter allows you to attach a unique identifier (such as a reference number) with the user undergoing the current journey. It's beneficial for connecting the journey to the user and/or the application that initiated it. You will receive this reference number in the webhook request.
  • locale: (Optional)This refers to the ISO code representing the desired language for the user interface components. Please reach out to IDWise Supportfor the list of available languages.

Here is definition for setJourneyMethodHandler()

Starting the Steps

When one of the following callback onJourneyStarted(...)or onJourneyResumed(...) is triggered successfully, you can call the IDWise.startStep(...) to start the specific verification Step.

Future<void> startStep(String stepId) async {
  print("StepId: $stepId");
  platformChannel.invokeMethod('startStep', {"stepId": stepId});
}
  • stepId : ID of the step you want to start. (Will be provided by IDWise for each step)

Journey and Step Events (Callbacks)

Throughout the journey, IDWise SDK sends back some events to the Hosting app. Here we can listen to those events:

Future<void> setJourneyMethodHandler() async {
    try {
      platformChannel.setMethodCallHandler((handler) async {
        switch (handler.method) {
          case 'onJourneyStarted':
            print("Method: onJourneyStarted, ${handler.arguments.toString()}");
		        //JOURNEY_ID[handler.arguments.toString()] will receive here when journey
        		//and can be used for later to resume a journey. [Refer to Sample]
            context.read<MyStore>().setJourneyId(handler.arguments.toString());
            break;
          case 'onJourneyFinished':
            print("Method: onJourneyFinished");
            break;
          case 'onJourneyCancelled':
            print("Method: onJourneyCancelled");
            break;
          case 'onJourneyResumed':
            print("Method: onJourneyResumed, ${handler.arguments.toString()}");
		        context.read<MyStore>().setJourneyId(handler.arguments.toString());
		        //JOURNEY_ID[handler.arguments.toString()] will receive here
        		// when journey is resumed. [Refer to Sample]
  	  	    getJourneySummary("<JOURNEY_ID>");
            break;
          case 'onStepCaptured':
            print("Method: onStepCaptured, ${handler.arguments.toString()}");
            break;
          case 'onStepConfirmed':
            print("Method: onStepConfirmed, ${handler.arguments.toString()}");
            break;
          case 'onStepResult':
            print("Method: onStepResult, ${handler.arguments.toString()}");
  	  	    getJourneySummary("<JOURNEY_ID>");
            break;
          case 'onError':
            print("Method: onError, ${handler.arguments.toString()}");
            break;
          case 'journeySummary':
            try {
               print("JourneySummary - Details: ${response["summary"].toString()}");
               print("JourneySummary - Error:  ${response["error"].toString()}");
               bool isCompleted = response["summary"]["is_completed"];
               print("JourneySummary - Completed: $isCompleted");

               if (isCompleted) {
                 context.read<MyStore>().setJourneyCompleted(true);
                 String? journeyId = await retrieveJourneyId();
                 platformChannel
                   .invokeMethod('finishDynamicJourney', {"journeyId": journeyId});
              	 clearSaved();
             	 }
             } catch (e) {
               print("Exception : JourneySummary: $e");
             }
            break;
          default:
            print('Unknown method from MethodChannel: ${handler.method}');
            break;
        }
      });

    } on PlatformException catch (e) {
      print("Failed : '${e.message}'.");
    }
}

Journey callback's usage are articulated below:

  • onJourneyStarted: This event is triggered when a user's journey begins. It serves to notify your application that the process has been initiated. Useful for logging or implementing any preparatory actions necessary for the journey.
  • onJourneyFinished: This callback is activated upon the completion of a user's journey. It signifies that the user has successfully navigated through all steps and finished the process. This is typically where you would implement any code required to handle the completion of the journey.
  • onJourneyCancelled: This event is fired if the user decides to cancel the journey midway. It indicates the user's choice to discontinue the process. This could be useful for providing user-specific feedback or to trigger re-engagement strategies.
  • onError: This event is invoked when an error occurs at any point during the journey. It allows your application to respond to any unexpected issues, ensuring smooth error handling and user experience. Bellow all possible errors.

Step callback's usage are articulated below:

  • onStepCaptured: This event is triggered when a user's capture an image or select an image from gallery. The purpose of this method is to handle the captured steps images.
  • onStepResult: This event is triggered with an outcome after complete of a submission and process. This is typically where you may handle the results of your submitted step.

Get Summary of the Verification Journey

You can get the Status of the journey anytime by calling the following function. You will receive the response against journeySummary event having the fields journeyId, journeyStepSummaries, journeyResult and journeyIsComplete.

Future<void> getJourneySummary(String journeyId) async {
  try {
		platformChannel.invokeMethod('getJourneySummary', {"journeyId": journeyId});
  } on PlatformException catch (e) {
    print("Failed : '${e.message}'.");
  }
}

Journey Summary contains following information

FieldTypeDescription
journey_idStringA unique identifier for the user journey. This is a string field representing a MongoDB ObjectId.
step_summariesList of StepSummaryAn array of objects that detail the steps the user has gone through in the journey. Each object in this array contains a definition and a result.
is_completedBooleanA boolean field that indicates if the journey is completed or not.

Step Summary contains following information

FieldTypeDescription
definitionStepDefinitionAn object that describes the step.
resultStepResultAn object that contains the result of the step.

Step Definition contains following information

FieldTypeDescription
step_idIntAn identifier for the step within the journey.

Step Result contains following information

FieldTypeDescription
recognitionDocumentRecognitionDocument recognition information
extractedFieldsMap of String and FieldValueInformation extracted from the document such as Name and Birth Date
hasPassedRulesBooleanWhether the step passes the validity checks
isConcludedBooleanWhether the user can still retry the step or not
statusStringProcessing status one of (In Progress, Submitted, Not Submitted)
errorUserFeedbackCodeStringError code for specific errors
errorUserFeedbackDetailsStringDetailed error description
errorUserFeedbackTitleStringShort message for error
nfcResultNFCResultResult from NFC chip reading

Error Feedbacks

Here's the table containing descriptions for each of the feedback codes:
Error User Feedback CodeDescription
feedback_failed_documentIndicates that the document submitted did not pass the validation checks. The user may need to resubmit with a clearer image or complete information.
feedback_unrecognised_documentSignifies that the system could not recognize the type of document submitted. Maybe the user submitted a bad quality image or not supported documents.
feedback_document_expiredIndicates that the submitted document has an expiration date that has already passed. The user needs to submit a valid, unexpired document.
feedback_proof_policy_failedSpecifies that the submitted document did not meet configured accepted document policy. This may require the user to check the guidelines and resubmit accordingly.
feedback_duplicated_document_foundSuggests that the document submitted has already been used in another submission. A different, unique document is required for proceeding.
feedback_wrong_sideIndicates that the wrong side of the document has been submitted. or when the right side is missed.
feedback_outdated_proof_of_addressSuggests that the proof of address is outdated and no longer valid for the current verification process. The user needs to submit a more recent proof of address.

Here is a full sample of how to start an ID verification flow.

Resuming a journey


Future<void> resumeDynamicJourney() async {
  try {
    final resumeJourneyArgs = {
      "journeyDefinitionId": "<JOURNEY_DEFINITION_ID>",
      "journeyId": "<JOURNEY_ID>"
      "locale": "en,
    };
    platformChannel.invokeMethod('resumeDynamicJourney', resumeJourneyArgs);

    setJourneyMethodHandler(); // Check Section 5.4

  } on PlatformException catch (e) {
    print("Failed : '${e.message}'.");
  }
}

This method takes all the same parameters. Only one parameter is replace from referenceNo to journeyId.

  • journeyId: This is a unique identifier that identifies your journey and it can be used to resume. This is received in the implementation of journeyCallback interface in onJourneyStarted method when you started journey first time.

Native Code to Start Journey

Inside your Activity which is extended from FlutterActivity, you need to override the configureFlutterEngine(flutterEngine: FlutterEngine) method. Add the following code inside this method to handle the initialize and startJourney requests from your flutter code e.g main.dart.

private var methodChannel: MethodChannel? = null

methodChannel = MethodChannel(
  flutterEngine.dartExecutor.binaryMessenger,
  "YOUR_CHANNEL_NAME" //Example: "com.idwise.fluttersampleproject/idwise"
)

methodChannel?.setMethodCallHandler { call, result ->
  when (call.method) {
    "initialize" -> {
      IDWise.unloadSDK()

      val clientKey = call.argument<String>("clientKey")
      val theme = when (call.argument<String>("theme")) {
        "LIGHT" -> IDWiseSDKTheme.LIGHT
        "DARK" -> IDWiseSDKTheme.DARK
        else -> IDWiseSDKTheme.SYSTEM_DEFAULT
      }

      IDWise.initialize(clientKey!!, theme) { error ->
        Log.d("IDWiseSDKCallback", "onError ${error?.message}")
        result.error("ERROR", error!!.message, null)
        methodChannel?.invokeMethod("onError", Gson().toJson(error))
      }
    }

    "startStep" -> {
      val stepId = call.argument<String>("stepId")
      stepId?.let { IDWise.startStep(this, it); }
    }

    "startStepFromFileUpload" -> {
      val stepId = call.argument<String>("stepId")
      stepId?.let { IDWise.startStepFromFileUpload(this, it,null); }
    }

    "startDynamicJourney" -> {
      val journeyDefinitionId = call.argument<String>("journeyDefinitionId")
      val referenceNo = call.argument<String>("referenceNo")
      val locale = call.argument<String>("locale")

      IDWise.startDynamicJourney(
        this,
        journeyDefinitionId!!,
        referenceNo,
        locale,
        journeyCallback = journeyCallback,
        stepCallback = stepCallback
      )
    }

    "resumeDynamicJourney" -> {
      val journeyDefinitionId = call.argument<String>("journeyDefinitionId")
      val journeyId = call.argument<String>("journeyId")
      val locale = call.argument<String>("locale")

      IDWise.resumeDynamicJourney(
        this,
        journeyDefinitionId!!,
        journeyId!!,
        locale,
        journeyCallback = journeyCallback,
        stepCallback = stepCallback
      )
    }

    "finishDynamicJourney" -> {
      val journeyId = call.argument<String>("journeyId")
      journeyId?.let { IDWise.finishDynamicJourney(it) }
    }

    "getJourneySummary" -> {
      val journeyId = call.argument<String>("journeyId")
      journeyId?.let {
        IDWise.getJourneySummary(it, callback = { summary, error ->
          val json = JSONObject()
          json.put("summary", Gson().toJson(summary))
          json.put("error", Gson().toJson(error))
          methodChannel?.invokeMethod("journeySummary", json.toString())
        })
      }
    }

    "unloadSDK" -> {
      IDWise.unloadSDK()
    }

    else -> result.error("NO_SUCH_METHOD", "NO SUCH METHOD", null)
  }
}