Flutter reference
Every public type in the FacePing Flutter package: setup, enrolment, verification, the camera widget and results.
Package: faceping (pub.dev, preview). Starter app: faceping/flutter-starter. Flutter 3.24 or later (Dart 3.5). Android 7 (API 24) or later, on 64-bit phones (arm64-v8a) and x86_64 emulators. iOS 15.1 or later; Xcode 26 or later.
flutter pub add faceping:1.0.0-preview.2
The package is a thin bridge over the native Android and iOS SDKs, so results, errors and timings are the same as in a native app. Everything is in package:faceping/faceping.dart.
Platform setup#
iOS. Set the minimum iOS version to 15.1: in Xcode, open ios/Runner.xcworkspace and set the Runner project's Minimum Deployments to 15.1 (with CocoaPods, also platform :ios, '15.1' in ios/Podfile). Add a camera usage description to ios/Runner/Info.plist; without it, iOS stops the app when the camera starts:
<key>NSCameraUsageDescription</key>
<string>We use the front camera to check it's you.</string>Flutter fetches the FacePing Swift package (faceping/faceping-ios) with Swift Package Manager, which current Flutter uses by default. If your app has Swift Package Manager turned off, the podspec downloads the same release (FacePing.xcframework.zip) during pod install and checks its SHA-256 before using it. The simulator has no front camera: test the live check on an iPhone.
Android. Nothing to add: the SDK (ai.faceping:faceping-android, from Maven Central) declares the CAMERA and INTERNET permissions. To keep your app small, build for the two ABIs FacePing supports:
android {
defaultConfig {
ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
}
}On a 32-bit Android phone the app still runs, and FacePing throws FacePingModelException so you can send people to your normal way in.
Setup#
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Returns at once and loads the face models in the background.
await FacePing.init(sandboxKey: 'fp_test_…'); // sandbox: faces enrolled on the device
// await FacePing.init(deviceToken: 'fpd_…'); // production: faces synced from your check-in list
runApp(const MyApp());
}
FacePing.current returns the same instance anywhere in your app, and throws StateError until init has completed (FacePing.currentOrNull returns null instead). Calling init again with the same options returns the same instance; with different options it throws FacePingInvalidOperationException (call close() first).
A key that doesn't start with fp_test_ throws FacePingSetupException from init. A key the server doesn't accept, or a first run with no connection, is reported by the first call that needs the network, so call sync() at start-up to find out early.
FacePingOptions#
For every option, call FacePing.initWithOptions(FacePingOptions(...)).
| Property | Type | Default | Notes |
|---|---|---|---|
sandboxKey |
String? |
null |
A fp_test_ key from Developers → Sandbox keys. |
deviceToken |
String? |
null |
Production. A fpd_ token for one check-in list. Set this or sandboxKey, not both. |
region |
FacePingRegion |
eu |
Your account's region (sets the API address). |
apiBaseUrl |
String? |
null |
Overrides the region's address, for example for the UK or US region. |
passiveLiveness |
bool |
true |
Passive liveness on every check. Turn it off only if your app runs its own liveness check first. |
autoSyncInterval |
Duration? |
5 minutes | Production only. null turns automatic sync off; call sync() yourself. |
FacePing#
class FacePing {
static Future<FacePing> init({String? sandboxKey, String? deviceToken, FacePingRegion region});
static Future<FacePing> initWithOptions(FacePingOptions options);
static FacePing get current;
static FacePing? get currentOrNull;
static Future<String> get sdkVersion; // the native SDK's version
FacePingOptions get options;
bool get isSandbox;
Future<EnrolResult> enrol(String id, FaceImage photo);
Future<VerifyResult> verifyLive(String id, FacePingCameraController camera);
Future<VerifyResult> verify(String id, FaceImage photo);
Future<bool> forget(String id);
Future<void> forgetAll();
Future<Set<String>> enrolledIds();
Future<SyncResult> sync();
Stream<SyncResult> get synced; // every sync, automatic or not
Future<void> close();
}
| Method | Notes |
|---|---|
enrol |
Sandbox only. Production enrolment happens with consent on your server or through the FacePing widget. Replaces any face already stored under id. |
verifyLive |
A live check on a FaceCamera: match, a head turn in a random direction, match again. Shows the prompts and sets the guide. About 10 seconds at most; it then returns noFace or the last failure. Stop it early with camera.cancel(). |
verify |
One photo, passive liveness only. |
forget |
Sandbox: deletes the face stored under id from the device now, and returns false if there was none. Production: throws FacePingInvalidOperationException; remove a person through your server instead. |
forgetAll |
Deletes every face on the device now. In production the next sync brings back the people still enrolled. |
enrolledIds |
Who can be verified on this device, as of the last enrol, forget or sync. |
sync |
Production: downloads the latest faces for the device's check-in list; a revoked device wipes its faces. Sandbox: activates the key, or renews its licence when due. |
close |
Stops automatic sync and frees the engine. FacePing.current is cleared; call init again to start over. |
FaceImage#
What you can pass as a photo.
| From | Example |
|---|---|
| The camera | await camera.capture() |
| JPEG or PNG bytes (HEIC too on iOS) | FaceImage.fromBytes(bytes) |
| A photo file on the device, for example from an image picker | FaceImage.fromFile(path) |
Photos are turned upright from their EXIF orientation and scaled down to 1280 pixels on the device before anything else happens. Bytes that aren't a readable image throw FacePingInvalidArgumentException when the photo is used.
FaceCamera#
The native front camera, with an oval face guide and the liveness prompts ("Turn your head left", "Look back at the camera"). The SDK runs the camera, so you don't need another camera package, and asks for camera permission the first time the widget appears. Give it a fixed height.
final camera = FacePingCameraController();
SizedBox(
height: 420,
child: FaceCamera(
controller: camera,
onCameraFailed: (message) => showMessage(message), // permission refused, or no front camera
),
)
FaceCamera |
Notes |
|---|---|
controller |
A FacePingCameraController. Create it once (for example in your State) and give it to one FaceCamera at a time. |
onCameraFailed |
Called when the camera can't start: permission refused or no front camera. The message says which. |
androidHybridComposition |
Android: Hybrid Composition, the default and the most faithful for camera previews. Set false for texture layer composition. |
FacePingCameraController |
Notes |
|---|---|
capture() |
Takes a still from the live view: upright, as the camera sees it (the preview is mirrored, the picture isn't). Throws FacePingCameraException if the camera doesn't deliver a picture within 5 seconds. |
setGuideState(state) |
How the guide looks: ready (dashed), working (a scan line), match (green) or noMatch (red). verifyLive sets it; set it yourself for other steps, for example working while enrolling. |
setShowGuide(show) |
Shows or hides the face outline. |
startCamera() |
Starts (or restarts) the camera, for example after your app got camera permission another way. |
cancel() |
Stops a live check on this camera; its verifyLive throws FacePingCancelledException. |
isAttached |
True while its FaceCamera is on screen. |
To translate or reword the prompts, use the native SDKs' strings: on Android the string resources faceping_prompt_look_at_camera, faceping_prompt_turn_left, faceping_prompt_turn_right and faceping_prompt_look_back; on iOS a FacePing.strings table with the keys faceping.prompt.lookAtCamera, faceping.prompt.turnLeft, faceping.prompt.turnRight and faceping.prompt.lookBack.
Backups#
The face store's encryption keys stay in the phone's Keychain or Keystore. On iOS, FacePing's folder is already excluded from backups. On Android, exclude files/faceping and the shared preferences faceping.keys from Auto Backup, or turn backup off.
Results#
class EnrolResult {
final EnrolOutcome outcome;
final String id;
bool get isEnrolled; // outcome == EnrolOutcome.enrolled
}
enum EnrolOutcome { enrolled, noFace, multipleFaces, faceTooSmall, notLive, limitReached }
class VerifyResult {
final VerifyOutcome outcome;
final double similarity; // cosine similarity; the match threshold is 0.363
final double? livenessScore; // passive liveness of the deciding frame
final Duration elapsed; // how long the check took on the device
final bool isSandbox;
bool get isMatch; // outcome == VerifyOutcome.match
}
enum VerifyOutcome {
match, noMatch, notEnrolled, noFace, livenessFailed, challengeFailed, expired, leaseExpired
}
class SyncResult {
final SyncStatus status; // ok, gone, revoked or unavailable
final int enrolled;
final DateTime? leaseUntil; // production: verifies offline until then
final DateTime? deleteAfter;
}
limitReached means the sandbox already holds 25 faces on this device: forget one first. leaseExpired (production) means the device hasn't synced for longer than its lease, 24 hours by default; it needs a connection before it verifies again.
Exceptions#
Everything FacePing throws is a FacePingException with a message ready to show.
| Exception | When |
|---|---|
FacePingSetupException |
The key or token is missing, invalid or revoked, or the sandbox key couldn't be activated (no connection on first run). |
FacePingModelException |
The face models couldn't load on this device. Send people to your normal way in. |
FacePingInvalidOperationException |
Not allowed in this mode or state, for example enrolling in production or a closed FacePing. |
FacePingInvalidArgumentException |
A bad argument: an empty id, or a photo that isn't a readable image. |
FacePingStorageException |
The device's secure storage failed. |
FacePingCameraException |
The camera isn't on screen, couldn't start or didn't deliver a picture. |
FacePingCancelledException |
A live check was stopped with camera.cancel(). |
Everything else is a result, not an exception: a face check never throws because someone looked away.
Testing#
package:faceping/faceping_platform_interface.dart exports FacePingPlatform. Set FacePingPlatform.instance to a fake in your unit tests to run your app's logic without a device.