sdk_ekyc_flutter 1.0.0
sdk_ekyc_flutter: ^1.0.0 copied to clipboard
Identity verification screens for Flutter that run on the phone: scan the front and back of a Vietnamese citizen ID or a passport photo page with a document liveness check, then a face liveness check [...]
sdk_ekyc_flutter #
Two full-screen Flutter widgets for identity verification. Everything runs on the phone; nothing is uploaded by the package.
| Widget | What the user does | What you get back |
|---|---|---|
IdDocumentScanner |
Holds a Vietnamese citizen ID (front, then back) or a passport photo page inside the frame and taps the shutter | The document cropped from the photo, plus the full frame |
FaceLivenessScanner |
Looks straight, then left, then right, following the text under the oval | A straight-on photo of the face |
A document liveness model checks every camera frame and tells the user what to fix: card not in the frame, wrong side, glare, a finger over the card, a photo of a screen. The face flow runs face detection and a liveness model on each frame.
Send the returned images to your own verification backend for OCR and face matching.
Requirements #
- Flutter 3.38.1 or newer (Dart 3.10). Tested on 3.47.7.
- Android:
compileSdk 36, Java 17,minSdkfrom your Flutter version (flutter.minSdkVersion). - iOS 12.0 or newer, on a real device. The simulator has no camera.
Install #
dependencies:
sdk_ekyc_flutter: ^1.0.0
flutter pub get
The package uses the official camera plugin from pub.dev. No overrides or git dependencies are needed.
Platform setup #
Android. In android/gradle.properties, add:
kotlin.jvm.target.validation.mode=warning
android.uniquePackageNames=false
tflite_flutter declares Java 11 and pulls in two LiteRT libraries that share a namespace. With Android Gradle Plugin 9 and Kotlin 2 (the default for new Flutter projects) both are build errors; these two lines turn them back into warnings. They do nothing on older projects.
Make sure android/app/build.gradle compiles against SDK 36 with Java 17:
android {
compileSdk 36
defaultConfig { minSdkVersion flutter.minSdkVersion }
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
The camera permission comes with the camera plugin.
iOS. Add both keys to ios/Runner/Info.plist. Without the camera key iOS terminates the app the first time the camera opens; the microphone key is required by App Store review because the camera plugin links the audio APIs, even though nothing is recorded.
<key>NSCameraUsageDescription</key>
<string>The camera is used to photograph your ID and your face for identity verification.</string>
<key>NSMicrophoneUsageDescription</key>
<string>The microphone is not used.</string>
If your Podfile sets a platform, use at least platform :ios, '12.0', then cd ios && pod install.
Scan an ID document #
import 'package:flutter/material.dart';
import 'package:sdk_ekyc_flutter/sdk_ekyc_flutter.dart';
class FrontOfIdPage extends StatefulWidget {
const FrontOfIdPage({super.key});
@override
State<FrontOfIdPage> createState() => _FrontOfIdPageState();
}
class _FrontOfIdPageState extends State<FrontOfIdPage> {
bool _checking = false;
Future<void> _onCaptured(DocumentScanResult result) async {
setState(() => _checking = false);
if (result.isAccepted) {
await result.closeCamera();
if (mounted) Navigator.of(context).pop(result.documentImagePath);
return;
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(result.message)));
await result.scanAgain();
}
@override
Widget build(BuildContext context) {
return Stack(children: [
IdDocumentScanner(
page: DocumentPage.citizenIdFront,
onCaptured: _onCaptured,
onBusyChanged: (busy) => setState(() => _checking = busy),
),
if (_checking) const Center(child: CircularProgressIndicator()),
]);
}
}
| Parameter | Type | |
|---|---|---|
page |
DocumentPage |
citizenIdFront, citizenIdBack (citizen ID or the older ID card) or passport |
onCaptured |
FutureOr<void> Function(DocumentScanResult) |
Called once after each shutter press |
onBusyChanged |
ValueChanged<bool> |
Optional. Called with true when the captured photo starts being checked |
DocumentScanResult:
| Member | |
|---|---|
isAccepted |
Whether the photo passed the document liveness check |
documentImagePath |
JPEG of the document only, cropped. This is the one to upload. Empty if the capture failed |
fullFrameImagePath |
JPEG of the whole camera frame. Empty if the capture failed |
message |
Vietnamese text for the user, for example why the photo was refused |
scanAgain() |
Resumes the camera so the user can retake the photo |
closeCamera() |
Releases the camera. Call it before leaving the screen |
pauseCamera() |
Keeps the camera paused |
The camera is paused when onCaptured runs and stays paused until you call scanAgain() or closeCamera(). The package never calls onBusyChanged(false); hide your progress indicator in onCaptured.
Face liveness #
FaceLivenessScanner(
texts: const FaceLivenessTexts(
lookStraight: 'Nhìn thẳng vào camera',
turnLeft: 'Quay mặt sang trái',
turnRight: 'Quay mặt sang phải',
completed: 'Hoàn tất',
),
onCompleted: (result) => Navigator.of(context).pop(result.faceImagePath),
onCameraUnavailable: () => Navigator.of(context).pop(),
)
| Parameter | Type | |
|---|---|---|
texts |
FaceLivenessTexts |
The line of text under the oval for each pose. There is no built-in text |
onCompleted |
FutureOr<void> Function(FaceCheckResult) |
Called once, when all three poses have passed. result.faceImagePath is the straight-on photo |
onProgress |
void Function(FaceCheckProgress) |
Optional. Called for every analysed frame while the user is moving: framePassed, status (what the detector saw), message |
onCameraUnavailable |
VoidCallback |
Optional. Permission refused or camera in use |
Each pose has a time limit; when it runs out the flow starts over by itself and onProgress reports status RESTART_ALL_STEP. Nothing is recorded.
Images #
All images are JPEG files in the app's temporary directory (getTemporaryDirectory()). The OS can remove that directory at any time, so upload or copy them straight away, and delete them once uploaded.
Network and privacy #
The package sends no images or user data anywhere. The only network access is google_fonts fetching Roboto from fonts.gstatic.com the first time a screen opens. To avoid it, bundle the Roboto .ttf files with your app and set GoogleFonts.config.allowRuntimeFetching = false in main().
Example app #
example/ opens each flow from a list: ID front, ID back, passport, face. Run it on a phone:
cd example && flutter run