I needed liveness detection in a Flutter app two years ago. The options were: pay for a commercial SDK (₹8-15L/year for the ones that actually worked on Indian Android hardware), roll a web-based flow through a WebView (latency kills it on 4G), or wire up Google ML Kit's face detection and write the challenge-response logic myself.
I wrote it myself. That package is livelyness_detection, and it now has 40 likes on pub.dev, a 160/160 quality score, and four separate developers have built derivative packages on top of it. One of those, m7_livelyness_detection, has officially discontinued itself and redirects to mine.
What it actually does
The package runs a camera feed through Google ML Kit's face detection, then walks the user through a sequence of active challenges: blink, turn left, turn right, smile. Each challenge has configurable thresholds (BlinkDetectionThreshold, SmileDetectionThreshold) so you can tune sensitivity per use case. The detection screen is a full-bleed camera preview with a frosted oval guide and an animated progress ring. One function call:
final CapturedImage? result = await LivelynessDetection.detect(
config: DetectionConfig(
steps: [
LivelynessStepItem(step: LivelynessStep.blink),
LivelynessStepItem(step: LivelynessStep.turnLeft),
LivelynessStepItem(step: LivelynessStep.smile),
],
startWithInfoScreen: true,
),
);
It pops a CapturedImage or null if the user cancels. The captured image includes which steps completed and, since v2.1.0, a PadResult with the passive anti-spoofing verdict.
Passive anti-spoofing, and why Phase 1 isn't enough
v2.1.0 added passive anti-spoofing (PAD). The idea: a real face has involuntary micro-movements. Head euler angles drift, eye-open probability jitters, the bounding box wobbles. A printed photo held still has near-zero variance across all of these. The analyzer watches a 45-frame sliding window (about 1.5 seconds at 30fps) and flags faces with flat variance as spoofs.
It catches flat printed photos reliably. It does not catch a hand-held printed photo, because your hand introduces enough jitter to pass. I put that in the security notes on day one because I've seen packages claim "anti-spoofing" without qualifying what that means. Phase 1 is a floor, not a ceiling.
v2.2.0 added Phase 2: an opt-in TFLite two-model ensemble (MiniFASNet at crop scales 2.7 and 4.0) that runs over a method channel. Both the heuristic check and the TFLite score have to pass. Either failing is a spoof. Fail-closed: if the TFLite model fails to load or score, the verdict is spoof, not inconclusive. I haven't tested this against screen replays or 3D masks yet.
The adoption I didn't plan for
I published the first version because I needed it for a client project. The adoption came from GitHub search results and pub.dev visibility. I didn't write blog posts or pitch it in Flutter communities. Some numbers as of November 2026:
- 19 independent repositories on GitHub import the package (GitHub code search for
livelyness_detectioninpubspec.yaml, excluding my own repos) - 24 forks
- 13 external contributors have opened issues or PRs
- 10 closed issues filed by developers I've never met, reporting real bugs from production use
Four derivative packages exist on pub.dev. The one that matters most is m7_livelyness_detection, which was a maintained fork for a while. Its pub.dev page now reads "DISCONTINUED" with a redirect to livelyness_detection. I didn't ask for that. The maintainer decided the fork wasn't worth maintaining separately.
The 160/160 score
pub.dev's scoring is automated: 30/30 Dart file conventions, 20/20 documentation, 20/20 platform support, 50/50 static analysis, 40/40 up-to-date dependencies. The score doesn't mean the package is good. It means the public API is documented, the analyzer is clean, null safety is correct, and platform declarations match actual platform support. I've seen packages with perfect scores that solve no real problem, and packages with 80/160 that I'd trust in production.
What the score does tell you: I maintained it. The 40/40 on dependencies means I kept them current across 7 versions over two years, which is the part most open-source packages quietly stop doing.
What I'd do differently
The name has a typo. livelyness isn't a word; liveliness is. I didn't catch it before the first pub.dev publish, and pub.dev package names are immutable. Every import, every pubspec.yaml entry, every GitHub search carries the misspelling. I thought about publishing under a corrected name and deprecating the original, but 19 repos and 4 derivative packages reference the current name. The cost of a rename is higher than the cost of the typo.
The v1 API used a builder pattern that made simple cases verbose. v2.0.0 was a breaking rewrite to a single detect() call. Breaking changes in a package with active users are expensive to ship even when they're right. I should have started with the simpler API.
What's next
Phase 2 TFLite is opt-in and I want to make it good enough to be the default. That means testing against screen replays (a phone showing a video of a face) and cheaper 3D masks. The MiniFASNet ensemble handles printed photos well but I don't have benchmark numbers for video replays yet.
The package is at pub.dev/packages/livelyness_detection. Source is at github.com/Kryonex-Labs/livelyness_detection. Issues and PRs welcome.
Top comments (0)