Flutter 4.0.4.x
Environment
Flutter
iOS
iOS 11 and above
Xcode "16.x"
Android
- Gradle Version 8.11.1
- minSdkVersion 21
- targetSdkVersion 35
Installation
Common
1. Add Trust Vision Flutter lib
Add to pubspec.yaml file under dependencies group:
trust_vision_plugin:
git:
# Replace the below with your specific info
url: to_be_replaced
ref: to_be_replaced
2. Precompile
$ flutter pub get
iOS
1. Add to podfile
# Add below lines to the end of podfile
post_install do |installer|
installer.pods_project.targets.each do |target|
// you can add more modules which have the error "Undefined symbol" into the list
if ['TensorFlowLiteC', 'TensorFlowLiteSwift', 'PromiseKit', 'CryptoSwift'].include? "#{target}"
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
end
2. Setup Xcode
Set Build Libraries for Distribution in Build Settings to No

3. Precompile
$ pod install
Android
Add to root-level build.gradle file (host app):
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
allprojects {
repositories {
google()
mavenCentral()
/*
Replace the below with your path to `trust_vision_plugin` Android repo which downloaded by `flutter pub get`.
*/
maven { url "$flutterRoot/.pub-cache/git/replace_by_git_repo_name-replace_by_commit_hash/android/repo" }
/*
For example:
maven { url "$flutterRoot/.pub-cache/git/tv_flutter_sdk_xyz-1a5eb61ccb44ffafa4d3557f4d9d8087bc1a4666/android/repo" }
*/
}
}
Add to app/build.gradle:
aaptOptions {
noCompress "tflite"
noCompress "lite"
}
// Support 16KB https://developer.android.com/guide/practices/page-sizes
ndk {
abiFilters "arm64-v8a", "armeabi-v7a"
}
Usage
import 'package:trust_vision_plugin/enums.dart';
import 'package:trust_vision_plugin/trust_vision_plugin.dart';
1. Initialize SDK
Before using any TV SDK functions, it needs to be successfully initialized once.
Init UIOnly mode
void initSDK() async {
try {
var initResult = await TrustVisionPlugin.instance.initialize(
jsonConfigurationByServer: jsonConfigurationByServer,
languageCode: languageCode,
endpoint: endpoint,
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
endpointLogger: endpointLogger,
accessKeyIdLogger: accessKeyIdLogger,
accessKeySecretLogger: accessKeySecretLogger,
xRequestId: xRequestId,
xRequestId2: xRequestId2,
imageEncryptionKey: imageEncryptionKey,
securityPublicKey: securityPublicKey,
flowId: flowId,
headers: headers,
);
} catch (exception) {
print("Handle exception: $exception");
}
}
Options:
- languageCode:
Stringthe code of the language that will show and sound to user. E.g Vietnamese (vi), English ( en). - imageEncryptionKey:
String(optional). the key to encrypt image data. It's optional. If it's null or empty then the image data will not be encrypted. - securityPublicKey:
String(optional). the key to encrypt exif data. It's optional. If it's null or empty then the exif data will be encrypted by the default key. - theme:
TVTheme(optional). The theme of the SDK. If it's null then the sdk will use the default theme or the theme that we customized for the client.
To call the APIs in the SDK, the host app needs to set the following parameters:
- endpoint:
String. The endpoint of the server. - accessKeyId:
String. The access key id of the server. - accessKeySecret:
String. The secret key of the server. - xRequestId:
String(optional) https://ekyc.trustingsocial.com/api-reference/customer-api#api-reconciliation - xRequestId2:
String(optional) - headers:
Map<String, String>(optional). The headers to be added to the request. - flowId:
String(optional). The flow id of the API
The host app will call the APIs itself, set the following parameters:
- jsonConfigurationByServer:
String. set this parameter if the host app will call the APIs itself. The jsonConfigurationByServer is the setting specialized for each client from TS server. It's the response json string get by API https://ekyc.trustingsocial.com/api-reference/customer-api/#get-client-settings. When it's null or unmatched with the expected type then the default setting in the SDK will be used.
If the log event feature is used, set the following parameters:
- endpointLogger:
String. The endpoint of the log event server. - accessKeyIdLogger:
String. The access key id of the log event server. - accessKeySecretLogger:
String. The secret key of the log event server.
2. ID card capturing
2.1. Configuration
Change the following parameters to suit your needs.
void _captureIdCard() async {
try {
TVCardType cardType = TVCardType(
id: "vn.national_id",
name: "CMND cũ / CMND mới / CCCD / Hộ chiếu",
hasBackSide: true,
orientation: TVCardOrientation.HORIZONTAL,
);
CardSide cardSide = CardSide.front; // CardSide.back
Map<String, dynamic> idCardConfig = {
"cardTypes": [cardType.toMap()],
"cardSide": cardSide.name,
"isEnableSound": true,
"isEnableSanityCheck": true,
"isReadBothSide": false,
"skipConfirmScreen": true,
"isEnableDetectingIdCardTampering": true,
"isEnableScanNfc": false,
"isEnableScanQr": false,
};
// Start ID card capturing
TVDetectionResult? result = await TrustVisionPlugin.instance.captureIdCard(idCardConfig);
print("_captureIdCard result: $result");
} catch (exception) {
print("Handle exception: $exception");
}
}
Where:
- cardType:
TVCardType - cardSide:
CardSide(back, front) - isEnableSound:
bool. enable/disable guiding sound - isEnableSanityCheck:
bool. Id card sanity checking should be enabled or not - isReadBothSide:
bool. whether or not read both sides of the selected id card at one flow - skipConfirmScreen:
bool. Whether or not TV SDK should skip its own confirmation screen. - isEnableDetectingIdCardTampering:
bool. enable/disable ID card tampering detection - isEnableScanNfc:
bool. enable/disable scan NFC flow - isEnableScanQr:
bool. enable/disable scan QRCode flow
2.2. Start ID card capturing
TVDetectionResult? result = await TrustVisionPlugin.instance.captureIdCard(idCardConfig);
2.2. Handle id result
2.2.1 Get front image
var idFrontImage = result?.idFrontImage;
// TODO call your api to upload image
2.2.2 Get back image
var idBackImage = result?.idBackImage;
// TODO call your api to upload image
2.2.3 Get qr image
var frontIdQr = result?.frontIdQr;
// TODO call your api to upload image
3. Selfie capturing
3.1. Configuration
Change the following parameters to suit your needs.
void _captureSelfie() async {
try {
LivenessMode livenessMode = LivenessMode.flash;
SelfieCameraMode selfieCameraMode = SelfieCameraMode.back; // or SelfieCameraMode.front, SelfieCameraMode.back
final selfieConfig = {
"cameraOption": selfieCameraMode.name,
"livenessMode": livenessMode.name,
"isEnableSound": true,
"isEnableSanityCheck": false,
"isEnableVerifyLiveness": false,
"skipConfirmScreen": true
};
// Start Selfie capturing
TVDetectionResult? result = await TrustVisionPlugin.instance.captureSelfie(
selfieConfig,
onNewFrameBatch: (TvFrameBatch frameBatch) {
// Handle frame batch
// Refer: https://ekyc.trustingsocial.com/sdks/Android-SDK#303-handle-onnewframebatch-callback
},
);
print("_captureSelfie result: $result");
} catch (exception) {
print("Handle exception: $exception");
}
}
Where:
- onNewFrameBatch:
Function(TvFrameBatch)?. This callback function can be called multiple times during the capturing process to return frames. - cameraOption:
SelfieCameraMode. Camera side - isEnableSound:
bool. enable/disable guiding sound - isEnableSanityCheck:
bool. Selfie sanity checking should be enabled or not - skipConfirmScreen:
bool. Whether or not TV SDK should skip its own confirmation screen. - livenessMode:
LivenessMode. Liveness mode
enum LivenessMode {
active,
passive,
none,
flash,
flash_edge,
flash_advanced,
flash_8,
flash_16,
flash_32
}
3.2. Start Selfie capturing
TVDetectionResult? result = await TrustVisionPlugin.instance.captureSelfie(selfieConfig);
3.3. Handle selfie result
Please don't remove any item from result.selfieImages list
3.3.1. Get frontal images
var selfieImages = result?.selfieImages;
var frontalImages = <TVImageClass?>[];
selfieImages?.forEach((item) {
if(item.frontalImage?.rawImageBase64 != null) {
// Get frontal image
frontalImages.add(item.frontalImage);
}
});
// TODO call your api to upload image
3.3.2. Get final frontal image (frontal step)
var selfieImages = result?.selfieImages;
var finalFrontalImage = selfieImages?.firstWhere((item) => item.gestureType?.toLowerCase() == 'frontal').frontalImage;
3.3.3. Get gesture images
var selfieImages = result?.selfieImages;
var gestureImages = <TVImageClass?>[];
selfieImages?.forEach((item) {
if(item.gestureImage?.rawImageBase64 != null) {
// Get gesture image
gestureImages.add(item.gestureImage);
}
});
// TODO call your api to upload image
3.3.4. Flash images
If you are using liveness modes such as flash, flash_edge, flash_advanced, flash_8, flash_16, or flash_32, the images should be handled as follows:
frontalImages[0]: Image captured during the close step.frontalImages[1]: Image captured during the far step.
4. NFC reader
4.1 Add NFC Capability

4.2 Set config Info.plist for iOS
<key>NFCReaderUsageDescription</key>
<string>This app would like to use NFC to scan CCCD chip</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
4.3. Configuration
Change the following parameters to suit your needs.
void _readNfc() async {
try {
final nfcConfig = {
"nfcCode": "12 digits of CCCD number",
"nfcSod": sod,
"cardIssueDate": issueDate,
"nfcCacheFields": nfcCacheFields,
"isRequestReadImageNfc": true,
"isRequestCloneDetectionNfc": true,
"isRequestIntegrityCheckNfc": true,
"nfcMaxRetries": 5,
"isEnableCheckNfcData": true,
"isEnableVerifyNfc": true,
"dateOfBirth": "dd/MM/yyyy",
"dateOfExpiry" : "dd/MM/yyyy"
"inputMode": inputMode,
"documentType": documentType,
};
// Start NFC reader
TVDetectionResult? result = await TrustVisionPlugin.instance.readNfc(nfcConfig);
print("_readNfc result: $result");
} catch (exception) {
print("Handle exception: $exception");
}
}
Where:
- nfcCode:
String12 digits of CCCD number - nfcSod:
String(optional) is the hash of SOD,hash_sodget from API /ekyc-core/nfc/check-nfc/ - cardIssueDate:
String(optional) is the issue date of ID card (dd/MM/yyyy) - nfcCacheFields:
List<String>(optional) is the list of fields that was cached from previous scanning,input_fieldsfrom from API /ekyc-core/nfc/check-nfc/ - isRequestReadImageNfc:
Bool. Read image in the chip when scan nfc or not - isRequestCloneDetectionNfc:
Bool. Check clone of the chip when scanning nfc or not - isRequestIntegrityCheckNfc:
Bool. Check integrity of the chip when scanning nfc or not - nfcMaxRetries:
Int. The maximum number of times the SDK retries an NFC scanning before giving up - isEnableCheckNfcData:
Bool. Enable check NFC data or not. If it's true then the SDK will call the API to get sod and cached fields of the NFC data. - isEnableVerifyNfc:
Bool. Enable verify NFC or not. If it's true then the SDK will call the API to verify the NFC data. - dateOfBirth:
String?(optional) is the date of birth (dd/MM/yyyy) - dateOfExpiry:
String?(optional) is the expired of ID card (dd/MM/yyyy). For cards with unlimited expiration, please input: 31/12/9999 - documentType:
String?(optional).vn.cccd_new(default) for CCCD or TCC.vn.passportfor Passport - inputMode:
String?(optional).manualfor inputnfcCode(default).mrzfor scan MRZ and scan NFC.
4.4. Start NFC reader
TVDetectionResult? result = await TrustVisionPlugin.instance.readNfc(nfcConfig);
4.5. Check if the device supports NFC.
bool? isSupported = await TrustVisionPlugin.instance.checkNfcSupport();
5. Scan QR code
5.1. Configuration
Change the following parameters to suit your needs.
void _scanQrCode () async {
try {
var selectedCardType = TVCardType(
id: "vn.national_id",
name: "CMND cũ / CMND mới / CCCD / Hộ chiếu",
hasBackSide: true,
orientation: TVCardOrientation.HORIZONTAL,
frontQr: TVQrSdk(
exist: true,
type: TVQRType.qrCode,
widthHeightRatio: 1,
)
);
final config = {
"cardTypes": [selectedCardType.toMap()],
"cardSide": CardSide.front.name,
"skipConfirmScreen": true,
"isEnableUploadFrames" : false,
"isEnableUploadImages" : false
};
TVDetectionResult? result = await TrustVisionPlugin.instance.scanQrCode(config);
} catch (exception) {
showSnackbar("Error: ${exception.toString()}");
}
}
Where:
- cardType:
TVCardType. Type of card - frontQr :
TVQrSdk. Qr setting - skipConfirmScreen:
bool. Whether or not TV SDK should skip its own confirmation screen. - isEnableUploadFrames:
Bool. Enable upload video frames or not. If it's false then the SDK won't call the API to upload the frames and the APIs that need the video frames will be skipped or called with empty frames data. - isEnableUploadImages:
Bool. Enable upload images or not. If it's false then the SDK won't call the API to upload the images and the APIs that need the image will be skipped.
TVQrSdk
- exist:
Bool. Has QR code or not. - type
TVQRType: Type of code:qrCode,barCode - widthHeightRatio
Float: Card ratio
5.2. Start scan QR code
TVDetectionResult? result = await TrustVisionPlugin.instance.scanQrCode(config);
5.3. Upload QR images
If the APIs is called by the SDK, please skip this step.
if result.frontIdQr.isRequired is true then result.frontIdQr.images array should be non-empty. Otherwise, clients should be warned to re-scan QR code.
QR images will be uploaded with this api: https://ekyc.trustingsocial.com/api-reference/customer-api/#upload-image
var frontQrImage = result?.frontIdQr?.images[i];
*The same logic will be applied to result.backIdQr
6. Result Handling:
6.1. Result Object:
result: .
TVDetectionResult:- cardType:
TVCardType. Card type - actionMode:
TVActionMode. Action Mode - cardInfoResult:
TVCardInfoResult. Card information result - livenessResult:
TVLivenessResult. Liveness result - idSanityResult:
TVSanityResult. Id card sanity result - selfieSanityResult:
TVSanityResult. Selfie sanity result - idTamperingResult:
TVSanityResult. ID tampering result - selfieImages:
[SelfieImage]. List of selfie image objects - idFrontImage:
TVImageClass. Id front image object - idBackImage:
TVImageClass. Id back image object - frontIdQr:
TVCardQr. Card's QR Code image. - nfcInfoResult:
TVNfcInfoResult. Card NFC information - selfieVideos:
[Uint8List]. Liveness video data - livenessFrameBatchIds:
[String]. Liveness valid frame batch id
- cardType:
SelfieImage:
- gestureImage:
String.UP | DOWN | LEFT | RIGHT | FRONTAL - frontalImage:
TVImageClass. Frontal image object - gestureImage:
TVImageClass. Gesture image object
- gestureImage:
TVImageClass:
- rawImageBase64:
String. Base64 string of image data - encryptedImageHexString:
String. Encrypted base64 string of image data - imageId:
String. Image id - metadata:
Map<String, dynamic>. Metadata of the image
- rawImageBase64:
TVCardInfoResult:
- infos:
[Info]array of Info - requestId:
String. Id of request
- infos:
Info:
- field:
String. Name of field example name, address... - value:
String. The value of each field
- field:
TVLivenessResult:
- score:
float. The score from 0 to 1 - isLive:
bool. Selfie image is live or not - requestId:
String. Id of request
- score:
TVSanityResult:
- isGood:
bool. Sanity is good or not - score:
float. The score from 0 to 1 - requestId:
String. Id of request - error:
String. Error message - details:
[Detail]. List of result detail
- isGood:
Detail:
- name:
String. Name of field - score:
String. The score from 0 to 1 - verdict:
String. Verdict of field
- name:
TVCardQr:
- images:
[TVImageClass]. List of image - isRequired:
bool.
if
isRequiredis true thenimagesarray should be non-empty. Otherwise, clients should be warned to re-capture id card photos.- images:
TVNfcInfoResult:
- requestId:
String. Id of request - com:
String. Com data - sod:
String. Sod data - dg1:
String. Dg1 data - dg2:
String. Dg2 data - dg13:
String. Dg13 data - dg14:
String. Dg14 data - dg15:
String. Dg15 data - infos:
[Info]. array of Info - verificationResult:
TVNfcVerificationStatus. Verification result data
- requestId:
TVNfcVerificationResult:
- verdict:
String. Result's verdict - cloneStatus:
TVNfcVerificationStatus. Clone status - integrityStatus:
TVNfcVerificationStatus. Integrity status - bcaStatus:
TVNfcVerificationStatus. BCA status (BCA is Bộ Công An)
- verdict:
TVNfcVerificationStatus:
- verdict:
TVNfcVerificationStatusVerdict | UNKNOWN,NOT_PRESENT,NOT_CHECKED,ALERT,GOOD,ERROR. Result's verdict - error:
TVDetectionError. Error detail
- verdict:
TvFrameBatch:
- id:
String?. Frame's id - frames:
[TvFrameClass]. Batch of framed to be pushed - metadata:
Map<String, dynamic>. Batch metadata to push - validVideoIds:
[String]. For debugging purpose only
- id:
TvFrameClass:
- index:
int?. Index of this frame in the list of recorded frames - base64:
String?. The data of the frame as a base64 String - label:
String. Label of this frame
- index:
TVDetectionError:
- errorCode:
int. Error code - detailErrorCode:
String. Detail for error code - errorDescription:
String. Error description
- errorCode:
5.2. Sanity result error handling
5.2.1. Id sanity
String? error = result?.idSanityResult?.error;
if (error != null) {
switch (result.idSanityResult.error) {
case 'image_too_blur':
// Image is too blurry
break;
case 'image_too_dark':
// Image is too dark
break;
case 'image_too_bright':
// Image is too bright
break;
case 'image_has_hole':
// Image has holes
break;
case 'image_has_cut':
// Images has been cut
break;
case 'image_has_hole_and_cut':
// Images has holes and has been cut
break;
}
}
5.2.2. Selfie sanity
String? error = result?.selfieSanityResult?.error;
if (error != null) {
switch (selfieSanityResult["error"]) {
case 'image_too_blur':
// Image is too blurry
break;
case 'image_too_dark':
// Image is too dark
break;
case 'image_too_bright':
// Image is too bright
break;
case 'not_white_background':
// The background is not white enough
break;
case 'not_qualified':
// The face is not qualified, could be occluded, covered or having something unusal
break;
case 'image_has_multiple_faces':
// Image has multiple faces
break;
case 'image_has_no_faces':
// Image does not have any face
break;
case 'right':
// Face is looking to the right
break;
case 'left':
// Face is looking to the left
break;
case 'open_eye,closed_eye':
// Right eye is closed
break;
case 'closed_eye,open_eye':
// Left eye is closed
break;
case 'open_eye,sunglasses':
// Sunglass covers the right eye
break;
case 'sunglasses,open_eye':
// Sunglass covers the left eye
break;
case 'closed_eye,closed_eye':
// Both eyes are closed
break;
case 'closed_eye,sunglasses':
// Left eye is closed, sunglass covers the right eye
break;
case 'sunglasses,closed_eye':
// Sunglass covers the right eye, right eye is closed
break;
case 'sunglasses,sunglasses':
// Sunglasses cover both eyes
break;
}
}
7. Error handling
switch (e.code) {
// connection errors
case 'timeout_error'
break;
// Network timeout. Poor internet connection.
case 'network_error':
// Network error. No internet connection
break;
// Id capturing
case 'incorrect_card_type':
// the input image is not same type with selected card
break;
case 'nocard_or_multicard_image':
// the input image is no card or multicard detected
break;
// Selfie capturing
case 'image_has_no_faces':
// face not detected in selfie image
break;
case 'image_has_multipe_faces':
// multiple faces are detected in selfie image
break;
// Common errors
case 'access_denied_exception':
break;
case 'invalid_parameter_exception':
break;
case 'rate_limit_exception':
break;
case 'internal_server_error':
break;
case: 'sdk_canceled':
break;
}