Flutter PUA Auth Plugin

A Flutter plugin that integrates The Whisper Company's PUA SDK for iOS and Android, providing biometric authentication and continuous face monitoring capabilities for enhanced security.

📋 Table of Contents

Overview

The Flutter PUA Auth plugin serves as a bridge between Flutter applications and native PUA SDKs (iOS and Android), offering:

  • Biometric Authentication: Face ID, Touch ID, Fingerprint authentication
  • Continuous Face Monitoring: Real-time face detection and counting from camera feed
  • Automatic Screen Locking: Lock/unlock based on face detection state
  • Multi-Face Detection: Detect and handle multiple faces (shoulder surfing protection)
  • Configurable Settings: Customize refresh rate, eyes-off-screen time, and maximum allowed faces

Features

🔐 Biometric Authentication

  • iOS: Face ID, Touch ID
  • Android: Fingerprint, Face Unlock
  • Automatic fallback to platform biometric APIs when PUA SDK is unavailable

👁️ Continuous Face Monitoring

  • Real-time face count detection from camera feed
  • Automatic screen locking when:
    • No face detected (user left)
    • Multiple faces detected (shoulder surfing)
  • Automatic unlocking when authorized user returns
  • Configurable monitoring parameters

⚙️ Configurable Parameters

  • Refresh Rate: Instant, fast, medium, light (default: Instant)
  • Eyes Off Screen Time: 0.25-30.0 seconds (default: 10.0)
  • Number of Faces Allowed: Minimum 1, no maximum (default: 1)
  • Low Light Threshold: 200-2000 lux (default: 400.0)

Platform Support

Platform Status Notes
iOS (Real Device) ✅ Fully Supported All features work: PUA SDK face monitoring, biometric auth
Android (Real Device) ✅ Fully Supported All features work: PUA SDK face monitoring, biometric auth
iOS Simulator ⚠️ Limited Support See Simulator/Emulator Behavior below
Android Emulator ⚠️ Limited Support See Simulator/Emulator Behavior below

Platform Feature Comparison

Feature iOS Android Notes
Version Detection ✅ Working ❌ Not Available Android SDK doesn't expose method
refreshRate ✅ Working ⚠️ Partial May not be set correctly
eyesOffScreenTime ✅ Working (2.0) ❌ Not Supported Android uses eyeClosedProbabilityThreshold
eyeClosedProbabilityThreshold ❌ Not Available ✅ Working iOS uses timeout-based approach
numberOfFacesAllowed ✅ Working (2.0) ✅ Working Set via PUAOne/PWATwo sub-objects
lowLightThreshold ✅ Working ⚠️ Partial May not be set correctly
onLightWarning callback ✅ Working ❌ Not Available Android SDK doesn't have this method
onAuthSuccess ✅ Working ✅ Working Both platforms fully supported
onAuthFailed ✅ Working ✅ Working Both platforms fully supported
Face count accuracy ✅ Exact ⚠️ Estimated Android estimates for IntruderFaceDetected
authenticateUser() face auth ✅ Working ⚠️ Partial Android requires startFaceMonitoring() active
BiometricPrompt face unlock N/A ⚠️ Limited Cannot force face unlock when both available

Simulator/Emulator Behavior

iOS Simulator

What Works:

  • ✅ Basic biometric authentication (authenticateUser()) - Uses LocalAuthentication framework
  • isBiometricAvailable() - Returns true if Face ID/Touch ID is enabled in simulator settings
  • getAvailableBiometrics() - Returns available biometric types
  • configureApiKey() - API key can be configured (but PUA SDK won't be used)
  • isApiKeyConfigured() - Returns true if API key is set

What Doesn't Work:

  • PUA SDK Face Monitoring - PUA.framework is device-only (arm64), not available on simulator (x86_64/arm64 simulator)
  • Continuous face detection - startFaceMonitoring() will not detect faces
  • Real-time face counting - Face count callbacks won't be triggered
  • Screen locking based on face detection - Not available

What Happens:

  • When you call startFaceMonitoring(), the plugin detects it's running on a simulator
  • It falls back to LocalAuthentication and basic platform APIs
  • The onFaceCountChanged callback may receive initial values (0 or 1) but won't update continuously
  • You'll see logs indicating "PUA SDK not available on simulator - using fallback"

To Test Face Monitoring:

  • You must use a physical iOS device - PUA SDK requires device hardware
  • Enable Face ID in device settings
  • Grant camera permission when prompted

Android Emulator

What Works:

  • ✅ Basic biometric authentication (authenticateUser()) - Uses Android BiometricPrompt API
  • isBiometricAvailable() - Returns true if biometric hardware is emulated
  • getAvailableBiometrics() - Returns available biometric types
  • configureApiKey() - API key can be configured
  • isApiKeyConfigured() - Returns true if API key is set

What Doesn't Work:

  • PUA SDK Face Monitoring - PUA SDK requires physical device with real camera
  • Continuous face detection - startFaceMonitoring() will not detect faces from camera
  • Real-time face counting - Face count callbacks won't be triggered
  • Screen locking based on face detection - Not available

What Happens:

  • When you call startFaceMonitoring(), the plugin attempts to use PUA SDK
  • If PUA SDK fails (common on emulator), it may fall back to ML Kit face detection (if available)
  • However, camera access on emulator is limited and may not work properly
  • You'll see logs indicating "PUA SDK not available" or camera-related errors

To Test Face Monitoring:

  • You must use a physical Android device - PUA SDK requires device hardware
  • Device must have biometric hardware (fingerprint or face unlock)
  • Grant camera permission when prompted

Real Device Behavior (iOS & Android)

What Works:

  • Full PUA SDK Integration - All features work as intended
  • Continuous Face Monitoring - Real-time face detection from camera
  • Automatic Screen Locking - Locks when no face or multiple faces detected
  • Biometric Authentication - Full Face ID/Touch ID/Fingerprint support
  • Configurable Settings - Refresh rate, eyes-off-screen time, max faces
  • Multiple Face Detection - Detects and reports multiple faces (exact count on iOS, estimated on Android)

Requirements:

  • Physical device with camera
  • Biometric hardware (Face ID, Touch ID, or Fingerprint)
  • Camera permission granted
  • Valid PUA API key configured
  • Internet connection (for initial PUA SDK authentication)

Expected Behavior:

  • Face monitoring starts immediately after startFaceMonitoring() is called
  • onFaceCountChanged callback receives continuous updates:
    • 0 when no face detected
    • 1 when authorized user present
    • >1 when multiple faces detected
  • Screen automatically locks/unlocks based on face detection state
  • Biometric authentication prompts appear when needed

Installation

1. Add Dependencies

Add the following to your pubspec.yaml:

dependencies:
  flutter_pua_auth:
    path: ../flutter_pua_auth  # or use git/pub.dev if published
  # Platform packages - MUST be explicitly added for auto-registration
  flutter_pua_auth_android:
    path: ../flutter_pua_auth/flutter_pua_auth_android
  flutter_pua_auth_ios:
    path: ../flutter_pua_auth/flutter_pua_auth_ios

⚠️ Important: You must add all three packages (flutter_pua_auth, flutter_pua_auth_android, flutter_pua_auth_ios) to your pubspec.yaml. The platform packages are required for auto-registration of native implementations.

2. Install Flutter Dependencies

flutter pub get

3. Platform-Specific Setup

iOS Setup

  1. Add PUA Framework:

    • Download PUA.framework from The Whisper Company
    • Place it in flutter_pua_auth_ios/ios/Frameworks/PUA.framework
    • The framework is automatically linked via podspec
  2. Add Permissions to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access required for face monitoring and authentication</string>
<key>NSFaceIDUsageDescription</key>
<string>Face ID authentication required for secure access</string>
  1. Install Pods:
cd ios
pod install
cd ..

Android Setup

  1. Add PUA AAR File:

    • Download the PUA SDK AAR from PUA SDK Android
    • Place the AAR file in flutter_pua_auth_android/android/libs/
    • The build.gradle is already configured to use it
  2. Add Permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera.any" />
  1. Required Dependencies (already included in build.gradle):
    • CameraX
    • ML Kit Face Detection
    • Biometric API
    • Room Database
    • Retrofit

Native Setup Requirements

⚠️ Important: After adding the package dependencies, you must configure the native iOS and Android projects to properly integrate the PUA SDK. These changes are required for the plugin to work correctly.

iOS Native Configuration

1. Update Podfile

Edit ios/Podfile and ensure:

# Set minimum iOS version to 13.0 (required by PUA SDK)
platform :ios, '13.0'

target 'Runner' do
  use_frameworks!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  
  # Manually add flutter_pua_auth_ios plugin
  pod 'flutter_pua_auth_ios', :path => '../../flutter_pua_auth_ios/ios'
  
  target 'RunnerTests' do
    inherit! :search_paths
  end
end

Key Changes:

  • Set platform :ios, '13.0' (not 12.0)
  • Add manual pod entry: pod 'flutter_pua_auth_ios', :path => '../../flutter_pua_auth_ios/ios'

2. Update AppDelegate.swift

Edit ios/Runner/AppDelegate.swift:

import Flutter
import UIKit
import flutter_pua_auth_ios

#if !targetEnvironment(simulator)
import PUA
#endif

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    // Register Flutter PUA Auth plugin manually
    FlutterPuaAuthPlugin.register(with: self.registrar(forPlugin: "flutter_pua_auth")!)
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Key Changes:

  • Add import flutter_pua_auth_ios
  • Add conditional import PUA (device-only, not for simulator)
  • Add manual plugin registration: FlutterPuaAuthPlugin.register(...)

3. Update Info.plist

Edit ios/Runner/Info.plist and add:

<key>NSCameraUsageDescription</key>
<string>Camera access required for face monitoring and security</string>
<key>NSFaceIDUsageDescription</key>
<string>Face ID authentication required for secure access</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access may be needed for certain features</string>

4. Update Xcode Project Deployment Target

In Xcode:

  1. Open ios/Runner.xcworkspace (not .xcodeproj)
  2. Select the Runner target
  3. Go to Build Settings
  4. Search for "iOS Deployment Target"
  5. Set it to 13.0 (not 12.0)

Or edit ios/Runner.xcodeproj/project.pbxproj and replace all occurrences:

  • IPHONEOS_DEPLOYMENT_TARGET = 12.0;IPHONEOS_DEPLOYMENT_TARGET = 13.0;

5. Update AppFrameworkInfo.plist

Edit ios/Flutter/AppFrameworkInfo.plist:

<key>MinimumOSVersion</key>
<string>13.0</string>

6. Run Pod Install

After making changes:

cd ios
pod install
cd ..

Android Native Configuration

1. Update build.gradle (or build.gradle.kts)

Edit android/app/build.gradle (or build.gradle.kts):

For Groovy (build.gradle):

android {
    namespace "com.example.your_app"
    compileSdkVersion 35
    ndkVersion "27.0.12077973"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    defaultConfig {
        applicationId "com.example.your_app"
        minSdkVersion 26  // CRITICAL: PUA SDK requires minimum SDK 26
        targetSdkVersion 35
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
}

// Add flatDir repository for AAR files from plugin
repositories {
    flatDir {
        dirs '../../flutter_pua_auth_android/android/libs'
    }
}

dependencies {
    // PUA SDK required dependencies (CRITICAL - all must be included)
    // CameraX - default implementation required by PUA SDK
    implementation "androidx.camera:camera-camera2:1.1.0-alpha07"
    implementation "androidx.camera:camera-lifecycle:1.1.0-alpha07"
    implementation "androidx.camera:camera-view:1.0.0-alpha27"
    
    // ML Kit for face detection
    implementation 'com.google.mlkit:face-detection:16.1.2'
    
    // Biometric authentication
    implementation 'androidx.biometric:biometric:1.2.0-alpha03'
    
    // Retrofit for network calls (required by PUA SDK)
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    
    // Kotlin coroutines (required by PUA SDK)
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
    
    // Room database (required by PUA SDK)
    def room_version = "2.3.0"
    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor "androidx.room:room-compiler:$room_version"
    implementation("androidx.room:room-ktx:$room_version")
}

For Kotlin DSL (build.gradle.kts):

android {
    namespace = "com.example.your_app"
    compileSdk = 35
    ndkVersion = flutter.ndkVersion

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    defaultConfig {
        applicationId = "com.example.your_app"
        minSdk = 26  // CRITICAL: PUA SDK requires minimum SDK 26
        targetSdk = 35
        versionCode = flutter.versionCode
        versionName = flutter.versionName
    }
}

// Add flatDir repository for AAR files from plugin
repositories {
    flatDir {
        dirs("../../flutter_pua_auth_android/android/libs")
    }
}

dependencies {
    // PUA SDK required dependencies (CRITICAL - all must be included)
    // CameraX - default implementation required by PUA SDK
    implementation("androidx.camera:camera-camera2:1.1.0-alpha07")
    implementation("androidx.camera:camera-lifecycle:1.1.0-alpha07")
    implementation("androidx.camera:camera-view:1.0.0-alpha27")
    
    // ML Kit for face detection
    implementation("com.google.mlkit:face-detection:16.1.2")
    
    // Biometric authentication
    implementation("androidx.biometric:biometric:1.2.0-alpha03")
    
    // Retrofit for network calls (required by PUA SDK)
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    
    // Kotlin coroutines (required by PUA SDK)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1")
    
    // Room database (required by PUA SDK)
    val room_version = "2.3.0"
    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")
    implementation("androidx.room:room-ktx:$room_version")
}

Key Changes:

  • Set minSdkVersion (or minSdk) to 26 (PUA SDK requirement)
  • Set compileSdkVersion (or compileSdk) to 35
  • Set Java/Kotlin compatibility to 1.8 (not 11)
  • Add flatDir repository pointing to plugin's libs directory
  • Add all PUA SDK required dependencies (CameraX, ML Kit, Biometric, Retrofit, Coroutines, Room)

2. Update AndroidManifest.xml

Edit android/app/src/main/AndroidManifest.xml and add permissions:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>
        <!-- Your app configuration -->
    </application>
    
    <!-- Required permissions for PUA SDK -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    <uses-feature android:name="android.hardware.camera.any" />
</manifest>

Key Changes:

  • Add INTERNET permission (for PUA SDK authentication)
  • Add CAMERA permission (for face monitoring)
  • Add USE_BIOMETRIC and USE_FINGERPRINT permissions (for biometric authentication)
  • Add uses-feature for camera hardware

3. Sync Gradle Files

After making changes:

cd android
./gradlew clean
cd ..

Or in Android Studio: File → Sync Project with Gradle Files

Flutter Code Configuration

Import Platform Packages

In your app's main.dart, you must import the platform packages to trigger auto-registration:

import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
// ignore: unused_import
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
// ignore: unused_import
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure PUA API key
  const String puaApiKey = "your-api-key-here";
  await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
  
  runApp(MyApp());
}

Why This Is Required:

  • The platform packages contain auto-registration code that runs when imported
  • Without these imports, the native implementations won't register with Flutter
  • You'll get MissingPluginException errors if these imports are missing

Verification Checklist

After completing all native setup steps, verify:

iOS:

  • ✅ Podfile has platform :ios, '13.0'
  • ✅ Podfile has manual pod entry for flutter_pua_auth_ios
  • ✅ AppDelegate.swift imports and registers the plugin
  • ✅ Info.plist has camera and Face ID permissions
  • ✅ Xcode project deployment target is 13.0
  • ✅ Ran pod install successfully

Android:

  • build.gradle has minSdkVersion 26
  • build.gradle has all PUA SDK dependencies
  • build.gradle has flatDir repository
  • AndroidManifest.xml has all required permissions
  • ✅ Gradle files synced successfully

Flutter:

  • pubspec.yaml includes all three packages (flutter_pua_auth, flutter_pua_auth_android, flutter_pua_auth_ios)
  • main.dart imports platform packages
  • ✅ API key configured in main()

Common Setup Errors

Error: MissingPluginException: No implementation found for method configureApiKey

Solution:

  • Ensure platform packages are imported in main.dart
  • Ensure platform packages are in pubspec.yaml dependencies
  • Run flutter clean && flutter pub get
  • Rebuild the app (not just hot reload)

Error: Could not find module 'PUA' (iOS)

Solution:

  • Ensure PUA.framework is in flutter_pua_auth_ios/ios/Frameworks/
  • Run pod install in ios/ directory
  • Check Podfile has manual pod entry

Error: CameraX is not configured properly (Android)

Solution:

  • Ensure camera-camera2 dependency is in build.gradle
  • Ensure all PUA SDK dependencies are added
  • Sync Gradle files

Error: Compiling for iOS 12.0, but module has minimum deployment target of iOS 13.0

Solution:

  • Update Podfile: platform :ios, '13.0'
  • Update Xcode project deployment target to 13.0
  • Update AppFrameworkInfo.plist MinimumOSVersion to 13.0
  • Run pod install again

Setup & Configuration

API Key Configuration

Why PUA API Key is Important

The PUA API key is essential for the plugin to function properly. Here's why:

  1. SDK Authentication: The PUA SDK requires a valid API key to authenticate and initialize

    • Without a valid API key, the PUA SDK will not start
    • Face monitoring will fail with "PUA API key not configured" error
  2. License Validation: The API key serves as a license to use the PUA SDK

    • Each API key is tied to your application/account
    • Invalid or expired keys will be rejected by the SDK
  3. Security: The API key ensures only authorized applications can use the PUA SDK

    • Protects The Whisper Company's intellectual property
    • Prevents unauthorized usage
  4. Feature Access: Valid API key enables all PUA SDK features:

    • Continuous face monitoring
    • Real-time face detection
    • Advanced authentication capabilities

⚠️ Important:

  • You must obtain a valid API key from The Whisper Company before using this plugin
  • The API key must be configured before calling startFaceMonitoring()
  • Without a valid API key, the plugin will fall back to basic platform biometric APIs (limited functionality)

How to Get PUA API Key

Contact The Whisper Company to obtain your PUA API key:

Configuration Methods

The PUA SDK requires an API key for authentication. You can configure it in two ways:

Configure the API key in your app's main.dart:

import 'package:flutter_pua_auth/flutter_pua_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure PUA API key
  const String puaApiKey = "your-api-key-here";
  await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
  
  runApp(MyApp());
}

Method 2: Android Resource File (Fallback)

For Android, you can also add the API key to android/app/src/main/res/values/pua_license.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="pua_api_key" translatable="false">your-api-key-here</string>
</resources>

Priority Order:

  1. Programmatic configuration (Method 1) - takes precedence
  2. Android resource file (Method 2) - used as fallback if Method 1 not called
  3. Error - if neither is configured, face monitoring will fail

Note: iOS only supports Method 1 (programmatic configuration).

API Reference

FlutterPuaAuth

The main class for interacting with PUA authentication and face monitoring. Access via singleton: FlutterPuaAuth.instance.

Core Methods

configureApiKey(String apiKey)

Configures the PUA API key. Must be called before using any PUA SDK features.

isApiKeyConfigured()

Checks if the PUA API key has been configured. Returns Future<bool>.

authenticateUser({String? reason, bool allowCredentials = false})

Performs biometric authentication. Returns Future<bool>.

Platform Notes:

  • iOS: Uses Face ID/Touch ID via LocalAuthentication
  • Android: Uses BiometricPrompt (fingerprint/face unlock). If startFaceMonitoring() is active, returns success immediately.

startFaceMonitoring({required Function(int) onFaceCountChanged, ...})

Starts continuous face monitoring from camera feed.

Parameters:

  • onFaceCountChanged (required): Callback with face count (0 = no face, 1 = authorized, >1 = multiple, -1 = eyes off)
  • refreshRate: 'Instant', 'fast', 'medium', or 'light' (default: 'Instant')
  • eyesOffScreenTime: 0.25-30.0 seconds (default: 10.0) - iOS only, Android uses eyeClosedProbabilityThreshold
  • numberOfFacesAllowed: Minimum 1, no maximum (default: 1)
  • lowLightThreshold: 200-2000 lux (default: 400.0)
  • onLowLightWarning: Optional callback - iOS only, Android SDK doesn't support this
  • onError: Optional callback for PUA SDK errors

Face Count Values:

  • 0: No face detected → Lock screen
  • 1: Authorized user → Unlock screen (auto re-authenticated)
  • >1: Multiple faces → Lock if exceeds numberOfFacesAllowed
  • -1: Eyes off screen → Lock screen

stopFaceMonitoring()

Stops face monitoring and releases camera resources. Always call in dispose().

isBiometricAvailable()

Checks if biometric authentication is available. Returns Future<bool>.

getAvailableBiometrics()

Gets available biometric types: ["face"], ["fingerprint"], or ["face", "fingerprint"].

getPuaVersion()

Gets PUA SDK version. Returns Future<String> (e.g., "2.0"). iOS only - Android returns default "2.0".

inspectNativeSdk()

Inspects native SDK classes to see all available methods/fields. Logs to native console (logcat/Xcode). Useful for debugging SDK integration. See HOW_TO_USE_SDK_INSPECTION.md for details.

Usage Examples

Basic Setup

In your app's main.dart, you must import the platform packages to trigger auto-registration:

import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
// ignore: unused_import
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
// ignore: unused_import
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure PUA API key
  const String puaApiKey = "your-api-key-here";
  await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
  
  runApp(MyApp());
}

⚠️ Important: The platform package imports are required for the plugin to work. Without them, you'll get MissingPluginException errors. See Native Setup Requirements for complete setup instructions.

Basic Authentication

import 'package:flutter_pua_auth/flutter_pua_auth.dart';

class AuthScreen extends StatefulWidget {
  @override
  _AuthScreenState createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  bool _isAuthenticated = false;

  Future<void> _authenticate() async {
    final authenticated = await FlutterPuaAuth.instance.authenticateUser(
      reason: 'Please authenticate to continue',
    );
    
    setState(() {
      _isAuthenticated = authenticated;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _isAuthenticated
          ? Text('Authenticated!')
          : ElevatedButton(
              onPressed: _authenticate,
              child: Text('Authenticate'),
            ),
      ),
    );
  }
}

Continuous Face Monitoring

import 'package:flutter_pua_auth/flutter_pua_auth.dart';

class SecureScreen extends StatefulWidget {
  @override
  _SecureScreenState createState() => _SecureScreenState();
}

class _SecureScreenState extends State<SecureScreen> {
  int _faceCount = 0;
  bool _isLocked = true;

  @override
  void initState() {
    super.initState();
    _startMonitoring();
  }

  Future<void> _startMonitoring() async {
    await FlutterPuaAuth.instance.startFaceMonitoring(
      onFaceCountChanged: (count) {
        setState(() {
          _faceCount = count;
          _isLocked = count != 1;
        });
      },
      refreshRate: 'Instant',
      eyesOffScreenTime: 2.0,
      numberOfFacesAllowed: 1,
    );
  }

  @override
  void dispose() {
    FlutterPuaAuth.instance.stopFaceMonitoring();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _isLocked
        ? Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.lock, size: 64),
                Text('Screen Locked'),
                Text('Face count: $_faceCount'),
              ],
            ),
          )
        : Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.lock_open, size: 64, color: Colors.green),
                Text('Screen Unlocked'),
                Text('Face count: $_faceCount'),
              ],
            ),
          ),
    );
  }
}

Complete Example with API Key Configuration

import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
// ignore: unused_import
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
// ignore: unused_import
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure PUA API key
  const String puaApiKey = "your-api-key-here";
  try {
    await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
    print('✅ PUA API key configured');
  } catch (e) {
    print('❌ Error configuring API key: $e');
  }
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _isMonitoring = false;
  int _faceCount = 0;
  String _status = 'Not monitoring';

  Future<void> _checkApiKey() async {
    final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
    if (!isConfigured) {
      setState(() {
        _status = 'API key not configured';
      });
    }
  }

  Future<void> _startMonitoring() async {
    final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
    if (!isConfigured) {
      _status = 'Please configure API key first';
      return;
    }

    await FlutterPuaAuth.instance.startFaceMonitoring(
      onFaceCountChanged: (count) {
        setState(() {
          _faceCount = count;
          if (count == 0) {
            _status = 'No face detected - Screen locked';
          } else if (count == 1) {
            _status = 'Face detected - Screen unlocked';
          } else {
            _status = '$count faces detected - Screen locked';
          }
        });
      },
      refreshRate: 'Instant',
      eyesOffScreenTime: 2.0,
      numberOfFacesAllowed: 1,
    );

    setState(() {
      _isMonitoring = true;
      _status = 'Monitoring started';
    });
  }

  Future<void> _stopMonitoring() async {
    await FlutterPuaAuth.instance.stopFaceMonitoring();
    setState(() {
      _isMonitoring = false;
      _status = 'Monitoring stopped';
      _faceCount = 0;
    });
  }

  @override
  void dispose() {
    if (_isMonitoring) {
      FlutterPuaAuth.instance.stopFaceMonitoring();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('PUA Auth Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Status: $_status', style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),
            Text('Face Count: $_faceCount', style: TextStyle(fontSize: 24)),
            SizedBox(height: 40),
            ElevatedButton(
              onPressed: _isMonitoring ? _stopMonitoring : _startMonitoring,
              child: Text(_isMonitoring ? 'Stop Monitoring' : 'Start Monitoring'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _checkApiKey,
              child: Text('Check API Key'),
            ),
          ],
        ),
      ),
    );
  }
}

Architecture

This plugin follows Flutter's federated plugin architecture:

Flutter App → FlutterPuaAuth → Platform Interface → Platform Implementation → Native PUA SDK

Communication:

  • Method Channel: One-time operations (configureApiKey, authenticateUser, etc.)
  • Event Channel: Continuous face count updates during monitoring

Platform Implementations:

  • iOS: flutter_pua_auth_ios → PUA.framework (device-only)
  • Android: flutter_pua_auth_android → PUA AAR library

See docs/ARCHITECTURE.md for detailed architecture documentation.

Simulator/Emulator Support

Feature iOS Simulator Android Emulator Real Device
API Key Configuration
Biometric Auth UI
Basic Authentication ✅ (LocalAuth) ✅ (BiometricPrompt) ✅ (PUA SDK)
Face Monitoring
Face Count Detection
Screen Locking

Note: Face monitoring features require physical devices with camera and biometric hardware.

Important Notes

⚠️ API Key Configuration

Critical: The PUA API key is required for the plugin to work properly.

  • Must be configured before use: Call configureApiKey() in main() before using any PUA SDK features
  • Required for face monitoring: Without a valid API key, startFaceMonitoring() will fail
  • SDK Authentication: The API key authenticates your app with the PUA SDK servers
  • License Validation: The API key validates your license to use the PUA SDK
  • iOS: Only supports programmatic configuration
  • Android: Supports both programmatic and resource file configuration (with fallback)

What Happens Without API Key:

  • ❌ Face monitoring will not work
  • ❌ PUA SDK will not initialize
  • ❌ You'll see "PUA API key not configured" errors
  • ⚠️ Plugin may fall back to basic platform APIs (limited functionality)

Always verify API key is configured:

final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
if (!isConfigured) {
  // Show error or configure API key
}

📱 Device Requirements

  • Physical Devices Only: PUA SDK requires physical devices with biometric hardware
  • iOS: iPhone/iPad with Face ID or Touch ID
  • Android: Device with fingerprint sensor or face unlock
  • Camera: Front-facing camera required for face monitoring
  • Internet: Required for initial PUA SDK authentication (one-time)

Why Physical Device?

  • PUA SDK uses device-specific hardware features
  • Camera access and face detection require real hardware
  • Simulators/emulators don't have the necessary hardware capabilities

Simulator/Emulator (Limited Testing Only)

iOS Simulator:

  • ✅ Can test basic biometric authentication UI
  • ✅ Can test API key configuration
  • Cannot test face monitoring - PUA SDK not available
  • Cannot test continuous face detection
  • Use for: UI testing, authentication flow testing
  • Don't use for: Face monitoring, real security testing

Android Emulator:

  • ✅ Can test basic biometric authentication UI
  • ✅ Can test API key configuration
  • Cannot test face monitoring - PUA SDK requires real camera
  • Cannot test continuous face detection
  • Use for: UI testing, authentication flow testing
  • Don't use for: Face monitoring, real security testing

⚠️ Important: Always test face monitoring features on real devices before deploying to production.

🔒 Permissions

  • Camera Permission: Required for face monitoring (requested automatically on Android, via Info.plist on iOS)
  • Biometric Permission: Required for authentication (handled automatically by platform)

👥 Multiple Face Detection

  • iOS: Provides exact face count (2, 3, 4, etc.) via onMultipleFacesDetected callback
  • Android: Provides estimated count based on lastKnownFaceCount + 1 when multiple faces detected (SDK limitation)
    • The Android PUA SDK's IntruderFaceDetected callback doesn't provide the exact face count
    • The plugin attempts to retrieve the actual count using reflection, but if unavailable, estimates it
    • If numberOfFacesAllowed = 2 and 2 faces are detected, Android will report 2 (within limit, won't lock)
    • If numberOfFacesAllowed = 2 and 3+ faces are detected, Android will report 3 or more (exceeds limit, will lock)

Note:

  • numberOfFacesAllowed has no maximum limit. You can set it to any value >= 1 (e.g., 5, 10, etc.)
  • The app will NOT lock when faceCount <= numberOfFacesAllowed - this is handled in the Flutter BLoC logic
  • Use inspectNativeSdk() to see what methods are available in the native SDK for face count detection

Lifecycle Management

Always call stopFaceMonitoring() in dispose() to prevent camera resource leaks.

Performance

  • Use 'light' or 'medium' refresh rate for better battery life
  • Face monitoring stops when app goes to background (platform limitation)

Troubleshooting

Common Issues

API Key Not Configured

  • Ensure configureApiKey() is called in main() before use
  • Verify API key is valid

Face Monitoring Not Working

  • Check API key: await FlutterPuaAuth.instance.isApiKeyConfigured()
  • Verify camera permission granted
  • Must run on physical device (not simulator/emulator)
  • Check logs for PUA SDK errors

Build Errors

  • iOS: Ensure PUA.framework is present, run pod install
  • Android: Ensure all dependencies in build.gradle, sync Gradle

Multiple Faces Not Detected (Android)

  • Android SDK limitation: IntruderFaceDetected doesn't provide exact count
  • Plugin estimates using lastKnownFaceCount + 1
  • Use inspectNativeSdk() to see available methods

SDK Inspection Not Working

  • Check logcat (Android): adb logcat | grep "SDK_INSPECTION"
  • Check Xcode console (iOS): search for [SDK_INSPECTION]
  • Ensure API key configured before inspection

License

Copyright © The Whisper Company. All rights reserved.

This plugin integrates with proprietary PUA SDKs from The Whisper Company. Please refer to The Whisper Company's licensing terms for SDK usage.

Support

For issues, questions, or contributions:

  • PUA SDK: Contact The Whisper Company
  • Plugin Issues: Open an issue in the project repository

Demo App

See the example/ directory for a complete demo application showcasing:

  • API key configuration
  • Biometric authentication
  • Continuous face monitoring
  • Settings screen for configuration
  • Screen locking/unlocking based on face detection

Libraries

flutter_pua_auth