faceping 1.0.0-preview.3
faceping: ^1.0.0-preview.3 copied to clipboard
Offline face verification with liveness for Flutter: enrol a face, then verify it on the device in under 0.3 s, with no network. A bridge over the native FacePing SDKs for Android and iOS.
FacePing for Flutter #
Offline face verification with liveness, for Flutter apps on Android and iOS. Enrol a face, then verify it on the device in under 0.3 s, with no network. 1:1 by default: you check that someone is the person they say they are.
This package is a thin bridge over the native FacePing SDKs (Android and iOS), so results, errors and timings are the same as in a native app.
final faceping = await FacePing.init(sandboxKey: 'fp_test_…');
// Add the face once, on the device
await faceping.enroll('me', photo);
// Verify any time, with no network
final result = await faceping.verifyLive('me', camera);
// result.outcome → VerifyOutcome.match
Preview. This is 1.0.0-preview.3. Get a free sandbox key at
app.faceping.ai/signup (Developers, Sandbox keys, Create sandbox key). Sandbox faces
are enrolled on the device, up to 25 per device, and deleted after 24 hours.
Requirements #
| Android | Android 7 (API 24) or later, on 64-bit phones (arm64-v8a) and x86_64 emulators |
| iOS | iOS 15.1 or later, on iPhones and the simulator; Xcode 26 or later |
| Flutter | 3.24 or later (Dart 3.5) |
On a 32-bit Android phone the app still runs, and FacePing reports FacePingModelException so you can send people to
your normal way in.
Set up #
flutter pub add faceping
iOS #
-
Set the minimum iOS version to 15.1: in Xcode, open
ios/Runner.xcworkspaceand set the Runner project's Minimum Deployments to 15.1. With CocoaPods, also setplatform :ios, '15.1'inios/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 (github.com/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, and FaceCamera asks for the camera the first time it appears.
To keep your app small, build for the two ABIs FacePing supports:
// android/app/build.gradle.kts
android {
defaultConfig {
ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
}
}
FacePing encrypts its face store with a key in the device's Android Keystore, which can't be restored on another
device. If your app uses Auto Backup, exclude files/faceping and the shared preferences faceping.keys.
Use it #
Set up once #
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. 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, for example sync().
The camera #
final camera = FacePingCameraController();
SizedBox(
height: 420,
child: FaceCamera(
controller: camera,
onCameraFailed: (message) => showMessage(message), // permission refused, or no front camera
),
)
FaceCamera shows the native front camera with an oval face guide and the liveness prompts ("Turn your head left",
"Look back at the camera"). It runs the camera itself, so you don't need another camera package.
Enrol and verify #
final faceping = FacePing.current;
// Sandbox: add a face on this device (in production, faces are added on your server, with consent).
final photo = await camera.capture();
final enrolled = await faceping.enroll('me', photo);
if (!enrolled.isEnrolled) showMessage('${enrolled.outcome}'); // for example EnrollOutcome.faceTooSmall
// A live check: match, a head turn in a random direction, match again. About 10 seconds at most.
final result = await faceping.verifyLive('me', camera);
if (result.isMatch) {
showMessage('Match in ${result.elapsed.inMilliseconds} ms');
}
You can also use a photo you already have: FaceImage.fromBytes(jpegBytes) or FaceImage.fromFile(path).
faceping.verify(id, photo) checks one photo with passive liveness only.
| Method | Notes |
|---|---|
enroll(id, photo) |
Sandbox only. Replaces any face already stored under id. |
verify(id, photo) |
One photo, passive liveness only. |
verifyLive(id, camera) |
The live check on a FaceCamera. Stop it early with camera.cancel(). |
forget(id) |
Sandbox: deletes that face from the device now. Returns false if there was none. |
forgetAll() |
Deletes every face on the device now. |
enrolledIds() |
Who can be verified on this device. |
sync() |
Production: downloads the latest faces for the device's check-in list. Sandbox: activates the key. |
synced |
A stream of every sync, automatic or not. |
close() |
Stops automatic sync and frees the engine. |
Results #
VerifyResult has outcome, similarity, livenessScore, elapsed (a Duration), isSandbox and isMatch.
VerifyOutcome |
Meaning |
|---|---|
match |
A live face that matches the enrolled one. |
noMatch |
A live face, but not the enrolled person. |
notEnrolled |
Nothing is enrolled under this id. |
noFace |
No usable face was seen. |
livenessFailed |
The face looked like a photo or a screen. |
challengeFailed |
The person didn't turn their head as asked in time. |
expired |
The faces on this device have expired and were deleted. |
leaseExpired |
Production: the device hasn't synced within its lease (24 hours by default). |
EnrollOutcome is one of enrolled, noFace, multipleFaces, faceTooSmall, notLive or limitReached (the
sandbox already holds 25 faces on this device).
Errors #
A face check never throws because someone looked away: that's an outcome. 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. |
FacePingCanceledException |
A live check was stopped with camera.cancel(). |
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.
Example #
The example app is FaceCheck, the same app as the native starters: enrol your face, then verify it with a live check, with or without a network.
cd example
flutter run --dart-define=FACEPING_SANDBOX_KEY=fp_test_yourkey
Licence #
The FacePing SDK licence: see LICENSE. You need a FacePing account to use it. The native SDKs include
third-party components under their own licences (YuNet, SFace, MiniFASNet, ONNX Runtime and others): see the
THIRD-PARTY-NOTICES.txt that ships inside them.