flutter_liveness_check 1.0.8 copy "flutter_liveness_check: ^1.0.8" to clipboard
flutter_liveness_check: ^1.0.8 copied to clipboard

A comprehensive Flutter package for face liveness detection with ML Kit.

🎭 Flutter Liveness Check Package #

A comprehensive Flutter package for face liveness detection with fully customizable UI, AppBar configuration, error handling, and advanced features. Built with ML Kit for accurate face detection, this package provides a complete solution for secure identity verification in mobile applications.

pub package License: MIT Platform

✨ Features #

  • 📷 Real-time Face Detection - ML Kit powered liveness verification
  • 🎨 Fully Customizable UI - Comprehensive theming and styling options
  • 🔧 Flexible AppBar - Custom titles, back buttons, and complete AppBar control
  • 📊 Status Management - Init, success, and fail states with asset replacement
  • 🔄 Retry Logic - Configurable maximum attempts with callbacks
  • 🌍 Localization Support - Customizable messages and button text
  • 🎯 Error Handling - Type-safe error enums with detailed error information
  • 🎭 Custom Widgets - Custom bottom widgets and loading overlays
  • ✏️ Font Customization - Set custom font family for all text elements
  • 🔍 Quality Detection - Advanced blur and lighting analysis
  • ⏸️ Pause/Resume Control - Pause and resume camera preview and face detection
  • 🎚️ Flexible Detection Modes - Enable/disable blink or smile detection independently
  • ⏱️ Photo Capture Delay - Configurable delay before capturing photo after verification

📋 Table of Contents #

🚀 Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  flutter_liveness_check: ^1.0.0

Then run:

flutter pub get

Platform Setup #

Android

Add the following permissions to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />

iOS

Add the following to your ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access for liveness verification</string>

Add the following to your Podfile:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      # You can remove unused permissions here
      # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',
        ## dart: PermissionGroup.camera
        'PERMISSION_CAMERA=1',
      ]
    end
  end
end

📱 Platform Support #

  • iOS: 10.0+ with Metal support
  • Android: API level 21+ (Android 5.0)
  • Flutter: 3.0.0 or higher
  • Dart: 2.17 or higher
  • Camera Permissions: Automatically requested with proper error handling
  • ML Kit: Google ML Kit Face Detection API
  • Hardware: Front-facing camera required

Supported Architectures #

  • Android: arm64-v8a, armeabi-v7a, x86_64
  • iOS: arm64, x86_64 (simulator)

🔄 Status Management #

The package supports three main states:

  • LivenessStatus.init: Shows camera preview with liveness detection
  • LivenessStatus.success: Shows success asset/animation
  • LivenessStatus.fail: Shows fail asset with retry button

🎭 Custom Assets #

Replace default success/fail assets:

LivenessCheckTheme(
  successAsset: 'packages/your_package/assets/custom_success.png',
  failAsset: 'packages/your_package/assets/custom_fail.png',
)

⚡ Quick Start #

Basic Usage #

import 'package:flutter/material.dart';
import 'package:flutter_liveness_check/flutter_liveness_check.dart';

class MyLivenessCheck extends StatefulWidget {
  @override
  State<MyLivenessCheck> createState() => _MyLivenessCheckState();
}

class _MyLivenessCheckState extends State<MyLivenessCheck> {
  LivenessStatus _status = LivenessStatus.init;

  @override
  Widget build(BuildContext context) {
    return LivenessCheckScreen(
      config: LivenessCheckConfig(
        status: _status,
        appBarConfig: const AppBarConfig(
          title: 'Face Verification',
          showBackButton: true,
        ),
        callbacks: LivenessCheckCallbacks(
          onSuccess: () => setState(() => _status = LivenessStatus.success),
          onError: (error) => setState(() => _status = LivenessStatus.fail),
          onTryAgain: () => setState(() => _status = LivenessStatus.init),
        ),
      ),
    );
  }
}

🎮 Manual Camera Control #

Use LivenessCheckController for advanced camera lifecycle management:

final controller = LivenessCheckController();

// Add controller to LivenessCheckScreen
LivenessCheckScreen(
  controller: controller,
  config: LivenessCheckConfig(...),
)

// Manual control methods
await controller.initializeCamera();  // Start camera + face detector
await controller.disposeCamera();     // Stop camera and release resources
controller.resetState();              // Reset blink count and smile status
await controller.pauseDetection();    // Pause camera preview and face detection
await controller.resumeDetection();   // Resume camera preview and face detection

// Monitor status
print(controller.isInitialized);  // Check if camera is ready
print(controller.isPaused);       // Check if camera is paused

// Listen to changes
controller.addListener(() {
  print('Camera status: ${controller.isInitialized}');
  print('Paused: ${controller.isPaused}');
});

Pause/Resume Detection #

Control camera preview and face detection without disposing the camera:

class _MyScreenState extends State<MyScreen> {
  final controller = LivenessCheckController();
  bool _showingDialog = false;

  Future<void> _showInstructions() async {
    // Pause detection while showing instructions
    await controller.pauseDetection();
    _showingDialog = true;

    await showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Instructions'),
        content: Text('Please position your face in the circle and blink 3 times'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('Got it'),
          ),
        ],
      ),
    );

    _showingDialog = false;
    // Resume detection after dialog closes
    await controller.resumeDetection();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LivenessCheckScreen(
        controller: controller,
        config: LivenessCheckConfig(...),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showInstructions,
        child: Icon(Icons.help),
      ),
    );
  }
}

App Lifecycle Management Example #

class _MyScreenState extends State<MyScreen> with WidgetsBindingObserver {
  final controller = LivenessCheckController();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    controller.dispose();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      // Use pauseDetection to save battery while keeping camera initialized
      controller.pauseDetection();
    } else if (state == AppLifecycleState.resumed) {
      // Resume detection when app comes back
      controller.resumeDetection();
    }
  }

  @override
  Widget build(BuildContext context) {
    return LivenessCheckScreen(
      controller: controller,
      config: LivenessCheckConfig(...),
    );
  }
}

📖 Complete Configuration Reference #

LivenessCheckConfig #

Main configuration container for all customization options:

LivenessCheckConfig(
  // AppBar Configuration
  appBarConfig: AppBarConfig(),

  // UI Components
  customBottomWidget: Widget?,
  customLoadingWidget: Widget?,

  // Visual Theme
  theme: LivenessCheckTheme(),

  // Behavior Settings
  settings: LivenessCheckSettings(),

  // Text & Localization
  messages: LivenessCheckMessages(),

  // Event Callbacks
  callbacks: LivenessCheckCallbacks(),

  // Status & Loading
  status: LivenessStatus.init,
  showLoading: false,
  placeholder: "Please smile or blink eye 3 time",
)

AppBarConfig - Complete AppBar Control #

AppBarConfig(
  // Basic Properties
  title: 'Custom Title',
  centerTitle: true,
  backgroundColor: Colors.blue,
  elevation: 4.0,

  // Back Button Control
  showBackButton: true,           // Show/hide back button
  automaticallyImplyLeading: true, // Flutter's automatic leading
  customBackIcon: IconButton(     // Custom back widget
    icon: Icon(Icons.close),
    onPressed: () => Navigator.pop(context),
  ),

  // Text Styling
  titleStyle: TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.bold,
    color: Colors.white,
  ),
)

LivenessCheckTheme - Visual Customization #

LivenessCheckTheme(
  // Colors
  backgroundColor: Colors.white,
  primaryColor: Colors.blue,
  successColor: Colors.green,
  errorColor: Colors.red,
  textColor: Colors.black,

  // Circle Styling
  borderStyle: CircleBorderStyle.solid, // or CircleBorderStyle.dashed
  borderColor: Colors.blue,
  borderWidth: 4.0,
  circleSize: 0.7, // 0.0 to 1.0
  cameraPadding: 8.0,

  // Dashed Border (when borderStyle = dashed)
  dashLength: 10.0,
  dashGap: 6.0,

  // Try Again Button Styling
  btnRetryBGColor: Colors.blue,
  btnTextRetryColor: Colors.white,
  btnRetryHeight: 44.0,              // Button height (default: 44)
  btnRetryPadding: EdgeInsets.symmetric(vertical: 16),
  btnRetryBorderRadius: 8.0,         // Border radius

  // Typography
  fontFamily: 'CustomFont',
  titleTextStyle: TextStyle(),
  messageTextStyle: TextStyle(),
  errorTextStyle: TextStyle(),
  successTextStyle: TextStyle(),

  // Assets
  successAsset: 'assets/custom_success.png',
  failAsset: 'assets/custom_fail.png',
)

LivenessCheckCallbacks - Event Handling #

LivenessCheckCallbacks(
  // Basic Events
  onSuccess: () {
    print('Liveness check passed!');
  },
  onError: (String errorMessage) {
    print('Error: $errorMessage');
  },
  onCancel: () {
    print('User cancelled');
  },

  // Advanced Events
  onErrorWithType: (LivenessCheckError errorType, String message) {
    switch (errorType) {
      case LivenessCheckError.noFaceDetected:
        // Handle no face error
        break;
      case LivenessCheckError.cameraPermissionDenied:
        // Handle permission error
        break;
      // ... handle other error types
    }
  },

  // Photo & Progress
  onPhotoTaken: (String imagePath) {
    print('Photo saved: $imagePath');
  },
  onProgressUpdate: (int blinkCount, bool isSmiling) {
    print('Progress: $blinkCount blinks, smiling: $isSmiling');
  },

  // Retry Logic
  onTryAgain: () {
    print('User retrying');
  },
  onMaxRetryReached: (int attemptCount) {
    print('Max attempts reached: $attemptCount');
  },
)

LivenessCheckSettings - Behavior Configuration #

LivenessCheckSettings(
  // Liveness Requirements
  enableBlinkDetection: true,     // Enable/disable blink detection
  requiredBlinkCount: 3,          // Number of blinks required
  enableSmileDetection: true,     // Enable/disable smile detection

  // UI Controls
  showProgress: true,
  showErrorMessage: true,
  showTryAgainButton: true,

  // Behavior
  autoNavigateOnSuccess: true,
  maxRetryAttempts: 3,
  processingTimeout: Duration(seconds: 30),

  // Layout
  circlePositionY: 0.38, // Vertical position (0.0 to 1.0)

  // Photo Capture
  photoCaptureDelay: Duration(milliseconds: 0), // Delay before capturing photo
)

LivenessCheckMessages - Text Customization #

LivenessCheckMessages(
  // AppBar
  title: 'Liveness Check',

  // Status Messages
  initializingCamera: 'Initializing camera...',
  noFaceDetected: 'No face detected. Please position your face in the circle.',
  multipleFacesDetected: 'Multiple faces detected. Only one person allowed.',
  livenessCheckPassed: 'Liveness check passed! Taking photo...',

  // Quality Messages
  moveCloserToCamera: 'Move closer to camera or hold device steady.',
  holdStill: 'Hold still. Face features not clear.',
  imageTooBlurry: 'Image too blurry. Hold device steady.',
  poorLighting: 'Poor lighting conditions.',

  // Error Messages
  failedToCapture: 'Failed to capture photo',
  cameraPermissionDenied: 'Camera permission denied',
  failedToInitializeCamera: 'Failed to initialize camera',

  // UI Text
  tryAgainButtonText: 'Try Again',
  takingPhoto: 'Taking photo...',

  // Permission Dialog Configuration
  permissionDialogConfig: PermissionDialogConfig(
    title: 'Camera Permission Required',
    message: 'Camera permission is required for liveness check. Please enable it in settings.',
    cancelButtonText: 'Cancel',
    settingsButtonText: 'Open Settings',
  ),
)

PermissionDialogConfig - Permission Dialog Customization #

When camera permission is permanently denied, a dialog will be shown to guide users to settings. Customize this dialog:

PermissionDialogConfig(
  title: 'Camera Access Needed',
  message: 'To verify your identity, we need access to your camera. Please enable it in your device settings.',
  cancelButtonText: 'Not Now',
  settingsButtonText: 'Go to Settings',
)

CameraSettings - Config camera controller #

enum ResolutionPreset {
  low,       // 352x288 (CIF)
  medium,    // 720x480 (NTSC)
  high,      // 1280x720 (HD)
  veryHigh,  // 1920x1080 (Full HD)
  ultraHigh, // 3840x2160 (4K)
  max        // Highest available, varies by device
}
enum ImageFormatGroup {
  unknown,
  jpeg,     // Compressed, smaller, good for storage/sharing
  yuv420,   // Raw format, great for real-time processing (default)
  bgra8888, // iOS-friendly, raw format
}

CameraSettings(
    //ResolutionPreset
    this.resolutionPreset = ResolutionPreset.high,
    //controls microphone input
    this.enableAudio = false,
    //ImageFormatGroup
    this.imageFormatGroup,
)

🎯 Usage Examples #

1. No Back Button Example #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: const AppBarConfig(
      title: 'Secure Verification',
      showBackButton: false, // Hide back button
    ),
    customBottomWidget: Container(
      padding: const EdgeInsets.all(16),
      child: ElevatedButton(
        onPressed: () => Navigator.pop(context),
        child: const Text('Done'),
      ),
    ),
  ),
)

2. Custom Back Icon with Confirmation #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: AppBarConfig(
      title: 'Identity Verification',
      showBackButton: true,
      customBackIcon: IconButton(
        icon: const Icon(Icons.close),
        onPressed: () {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              title: const Text('Cancel Verification?'),
              content: const Text('Are you sure you want to cancel?'),
              actions: [
                TextButton(
                  onPressed: () => Navigator.pop(context),
                  child: const Text('No'),
                ),
                TextButton(
                  onPressed: () {
                    Navigator.pop(context); // Close dialog
                    Navigator.pop(context); // Close liveness screen
                  },
                  child: const Text('Yes'),
                ),
              ],
            ),
          );
        },
      ),
    ),
  ),
)

3. Banking App Integration #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: AppBarConfig(
      title: 'SecureBank Verification',
      backgroundColor: const Color(0xFF1565C0),
      titleStyle: const TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
      showBackButton: true,
      customBackIcon: CustomBackButton(
        onPressed: () => _handleBankingCancel(context),
      ),
    ),
    theme: const LivenessCheckTheme(
      primaryColor: Color(0xFF1565C0),
      borderStyle: CircleBorderStyle.dashed,
      fontFamily: 'Roboto',
    ),
    callbacks: LivenessCheckCallbacks(
      onErrorWithType: (errorType, message) {
        _handleBankingError(errorType, message);
      },
      onSuccess: () => _proceedToBankingApp(),
    ),
  ),
)
LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: const AppBarConfig(
      title: 'Quick Verification',
      showBackButton: true,
    ),
    settings: const LivenessCheckSettings(
      enableBlinkDetection: true,     // Enable blink detection
      requiredBlinkCount: 3,          // Require 3 blinks
      enableSmileDetection: false,    // Disable smile detection
      maxRetryAttempts: 3,
    ),
    placeholder: 'Please blink your eyes 3 times',
    callbacks: LivenessCheckCallbacks(
      onSuccess: () => _handleSuccess(),
    ),
  ),
)
LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: const AppBarConfig(
      title: 'Smile Verification',
      showBackButton: true,
    ),
    settings: const LivenessCheckSettings(
      enableBlinkDetection: false,    // Disable blink detection
      enableSmileDetection: true,     // Enable smile detection
      maxRetryAttempts: 3,
      photoCaptureDelay: Duration(milliseconds: 500), // Wait 500ms before capture
    ),
    placeholder: 'Please smile for the camera',
    callbacks: LivenessCheckCallbacks(
      onSuccess: () => _handleSuccess(),
    ),
  ),
)

6. E-commerce KYC #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    appBarConfig: const AppBarConfig(
      title: 'Account Verification',
      showBackButton: false, // No back button for KYC
    ),
    settings: const LivenessCheckSettings(
      enableBlinkDetection: true,
      requiredBlinkCount: 2,
      enableSmileDetection: true,
      maxRetryAttempts: 5,
      photoCaptureDelay: Duration(milliseconds: 0), // Immediate capture
    ),
    theme: const LivenessCheckTheme(
      primaryColor: Color(0xFFFF6B35),
      borderStyle: CircleBorderStyle.solid,
      circleSize: 0.75,
    ),
    customBottomWidget: _buildKYCInstructions(),
    callbacks: LivenessCheckCallbacks(
      onSuccess: () => _submitKYCData(),
      onMaxRetryReached: (count) => _showKYCFailure(count),
    ),
  ),
)

7. Custom Loading Widgets #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    // Custom camera initialization loading (shown while camera starts)
    customCameraLoadingWidget: Container(
      color: Colors.white,
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CircularProgressIndicator(color: Colors.purple),
            const SizedBox(height: 16),
            const Text('Initializing camera...'),
          ],
        ),
      ),
    ),

    // Custom processing overlay (shown during verification)
    showLoading: _isProcessing,
    customLoadingWidget: Container(
      color: Colors.black87,
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CircularProgressIndicator(color: Colors.purple),
            const SizedBox(height: 16),
            const Text(
              'Processing verification...',
              style: TextStyle(color: Colors.white),
            ),
          ],
        ),
      ),
    ),

    callbacks: LivenessCheckCallbacks(
      onErrorWithType: (errorType, message) {
        switch (errorType) {
          case LivenessCheckError.cameraPermissionDenied:
            _showPermissionDialog();
            break;
          case LivenessCheckError.poorLighting:
            _showLightingTips();
            break;
          default:
            _showGenericError(message);
        }
      },
    ),
  ),
)

8. Custom Permission Dialog #

LivenessCheckScreen(
  config: LivenessCheckConfig(
    messages: LivenessCheckMessages(
      permissionDialogConfig: PermissionDialogConfig(
        title: 'Camera Access Required',
        message: 'We need camera access to verify your identity. Please grant permission in your device settings.',
        cancelButtonText: 'Not Now',
        settingsButtonText: 'Open Settings',
      ),
    ),
  ),
)

🎨 Theming Examples #

Dark Theme #

const LivenessCheckTheme(
  backgroundColor: Color(0xFF1E1E1E),
  primaryColor: Colors.purple,
  successColor: Colors.teal,
  errorColor: Colors.orange,
  textColor: Colors.white,
  overlayColor: Color(0xFF1E1E1E),
  borderStyle: CircleBorderStyle.dashed,
  dashLength: 12.0,
  dashGap: 8.0,
)

Corporate Theme #

const LivenessCheckTheme(
  backgroundColor: Color(0xFFF8F9FA),
  primaryColor: Color(0xFF1565C0),
  successColor: Color(0xFF2E7D32),
  errorColor: Color(0xFFD32F2F),
  fontFamily: 'Corporate-Font',
  borderWidth: 6,
  circleSize: 0.8,
)

🔧 Error Handling #

The package provides type-safe error handling with detailed error information:

// Error Types
enum LivenessCheckError {
  cameraPermissionDenied,
  cameraInitializationFailed,
  noFaceDetected,
  multipleFacesDetected,
  imageBlurry,
  faceNotClear,
  moveCloserToCamera,
  poorLighting,
  photoCaptureFailed,
  processingTimeout,
  unknownError,
}

// Usage
LivenessCheckCallbacks(
  onErrorWithType: (LivenessCheckError errorType, String message) {
    // Handle specific error types
    _analytics.logError(errorType.toString(), message);
    _showUserFriendlyError(errorType);
  },
)

🌍 Localization #

Easily localize all text by providing custom messages:

// Spanish Localization
const LivenessCheckMessages(
  title: 'Verificación de Vida',
  noFaceDetected: 'No se detectó rostro. Posicione su cara en el círculo.',
  multipleFacesDetected: 'Se detectaron múltiples rostros. Solo una persona.',
  livenessCheckPassed: '¡Verificación exitosa!',
  tryAgainButtonText: 'Intentar Nuevamente',
)

⚡ Performance Tips #

Optimize for Better Performance #

  1. Camera Settings

    // Use medium resolution for better performance
    ResolutionPreset.medium // Default and recommended
    
  2. Memory Management

    // Dispose properly when done
    @override
    void dispose() {
      // Liveness screen handles disposal automatically
      super.dispose();
    }
    
  3. Frame Processing

    LivenessCheckSettings(
      processingTimeout: Duration(seconds: 20), // Adjust based on device performance
    )
    
  4. Quality Detection

    • Enable quality detection to guide users for better images
    • Use appropriate lighting conditions
    • Ensure device stability during capture

Battery Optimization #

  • Camera operations are automatically suspended when app goes to background
  • Face detection stops when liveness check is complete
  • ML Kit resources are released when screen is disposed

🔧 Troubleshooting #

Common Issues #

Camera Permission Denied

LivenessCheckCallbacks(
  onErrorWithType: (errorType, message) {
    if (errorType == LivenessCheckError.cameraPermissionDenied) {
      // Guide user to settings
      _showPermissionSettingsDialog();
    }
  },
)

Poor Detection Quality

LivenessCheckCallbacks(
  onErrorWithType: (errorType, message) {
    switch (errorType) {
      case LivenessCheckError.poorLighting:
        _showLightingTips();
        break;
      case LivenessCheckError.imageBlurry:
        _showStabilityTips();
        break;
      case LivenessCheckError.moveCloserToCamera:
        _showDistanceTips();
        break;
    }
  },
)

Multiple Faces Detected

  • Ensure only one person is visible in the camera frame
  • Check for reflections or images in the background

Debug Mode #

Enable debug logging for development:

// Add this in your main.dart for debug builds
import 'package:flutter/foundation.dart';

void main() {
  if (kDebugMode) {
    // Debug prints are automatically enabled in the package
  }
  runApp(MyApp());
}

🚨 Migration Guide #

If upgrading from a version that used customHeader, replace it with appBarConfig:

// Before
LivenessCheckConfig(
  customHeader: AppBar(title: Text('Custom')),
)

// After
LivenessCheckConfig(
  appBarConfig: AppBarConfig(
    title: 'Custom',
    showBackButton: true,
  ),
)
2
likes
140
points
446
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter package for face liveness detection with ML Kit.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

camera, device_info_plus, flutter, google_mlkit_face_detection, image, path_provider, permission_handler

More

Packages that depend on flutter_liveness_check