Flutter Implementation

Flutter Installation & Setup

Project Requirements

Before Flutter SDK integration, ensure your environment meets these requirements:

  • Flutter: 3.0.0+
  • Dart: 3.0.0+
  • Android: API level 21+ (Android 5.0+)
  • iOS: 11.0+
  • Xcode: 12.0+ (for iOS builds)

Installation

Add the Kora Liveness Flutter SDK to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  kora_identity_flutter_sdk: ^1.0.0

Then run:

flutter pub getPlatform Setup

Android Configuration

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.INTERNET" />
    
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    
    <application
        android:usesCleartextTraffic="true">
        </application>
</manifest>

iOS Configuration

Add camera usage descriptions to your ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>This app needs access to camera for identity verification and liveness checks</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for enhanced verification experience</string>

Ensure your iOS deployment target is set to 11.0 or higher in ios/Runner.xcodeproj/project.pbxproj:

IPHONEOS_DEPLOYMENT_TARGET = 11.0;

Basic Import and Setup:

import 'package:kora_identity_flutter_sdk/kora_identity_flutter_sdk.dart';

Flutter Integration Workflow

This section outlines the steps to integrate with our Flutter SDK.

Step 1: SDK Import and Setup

Import the necessary components from the SDK:

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

class VerificationScreen extends StatefulWidget {
 @override
  _VerificationScreenState createState() => _VerificationScreenState();
}

class _VerificationScreenState extends State<VerificationScreen> {
  final KoraLivenessService _livenessService = koraLivenessService;
  bool _isLoading = false;
  LivenessResult? _result;
}

Step 2: Collect Verification Details

Prepare the configuration object with user details and verification settings:

final verificationConfig = KoraLivenessOptionsExtended(
  user: LivenessUser(
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
  ),
  branding: LivenessBranding(
    color: '#2376F3',
    logo: 'https://your-domain.com/logo.png',
    name: 'Your Company',
  ),
  presentation: 'modal',
  allowAudio: false,
  debugMode: true,
  sandboxEnvironment: true,
);

Step 3: Start Verification Flow

Option A: Auto-Determined Type (Recommended)

Let the backend determine the best verification type:

Future<void> _startAutoVerification() async {
  setState(() => _isLoading = true);

  try {
    // Initialize the service
    await _livenessService.initialize(
      publicKey: 'your_public_key_here',
      livenessOptions: KoraLivenessOptions(
        debugMode: true,
        sandboxEnvironment: true,
      ),
    );
    
    // Start verification with auto-detection
    final component = await _livenessService.checkLiveness(
      KoraLivenessOptionsExtended(
        // No type specified - backend auto-determines based on risk
        user: LivenessUser(
          firstName: 'John',
          lastName: 'Doe',
          email: '[email protected]',
        ),
        branding: LivenessBranding(
          color: '#2376F3',
          logo: 'https://your-domain.com/logo.png',
          name: 'Your Company',
        ),
        presentation: 'modal',
        allowAudio: false,
        onSuccess: (result) {
          setState(() {
            _result = result;
            _isLoading = false;
});
          print('Verification successful: ${result.data}');
          // Handle success - user verified as live human
        },
        onFailure: (result) {
          setState(() {
            _result = result;
            _isLoading = false;
          });
          print('Verification failed: ${result.error}');
          // Handle failure - verification did not pass
        },
        onClose: () {
          setState(() => _isLoading = false);
          print('User closed verification');
          // Handle user cancellation
        },
        onStart: () {
          print('Verification started');
        },
      ),
    );
    
    // Navigate to verification screen or display component
    if (component != null && mounted) {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => Scaffold(
            body: component as Widget,
          ),
        ),
      );
    }
    
  } catch (error) {
  setState(() => _isLoading = false);
    print('Failed to start verification: $error');
  }
}

Option B: Specific Verification Types

For specific verification types:

// Passive Liveness (Frictionless, good for low-risk flows)
Future<void> _startPassiveVerification() async {
  await _livenessService.initialize(
    publicKey: 'your_public_key_here',
    livenessOptions: KoraLivenessOptions(debugMode: true, sandboxEnvironment: true),
  );

  final component = await _livenessService.checkLiveness(
    KoraLivenessOptionsExtended(
      type: 'passive',
      user: LivenessUser(firstName: 'John', lastName: 'Doe', email: '[email protected]'),
      // ...callbacks
    ),
  );
}

// Active Liveness (Interactive, good for high-assurance flows)
Future<void> _startActiveVerification() async {
  await _livenessService.initialize(
    publicKey: 'your_public_key_here',
    livenessOptions: KoraLivenessOptions(debugMode: true, sandboxEnvironment: true),
  );

  final component = await _livenessService.checkLiveness(
	KoraLivenessOptionsExtended(
      type: 'active',
      user: LivenessUser(firstName: 'John', lastName: 'Doe', email: '[email protected]'),
      tasks: [
        {
          'id': 'complete_the_circle',
          'difficulty': 'medium',
          'timeout': 30000,
        },
        {
          'id': 'blink',
          'difficulty': 'easy',
          'maxBlinks': 3,
          'timeout': 15000,
        }
      ],
      allowAudio: true,
      // ...callbacks
    ),
  );
}

Step 4: Handle the Verification Component

Render the verification component in your Flutter application:

class VerificationApp extends StatefulWidget {
  @override
  _VerificationAppState createState() => _VerificationAppState();
}

class _VerificationAppState extends State<VerificationApp> {
  Widget? _livenessComponent;
  LivenessResult? _result;
  bool _isLoading = false;
@override
  Widget build(BuildContext context) {
    if (_livenessComponent != null) {
      return Scaffold(
        body: _livenessComponent!,
      );
    }

    return Scaffold(
      appBar: AppBar(title: Text('Identity Verification')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (_isLoading)
              CircularProgressIndicator()
            else
              ElevatedButton(
                onPressed: _handleVerification,
                child: Text('Start Verification'),
              ),
            
            if (_result != null)
              Padding(
                padding: EdgeInsets.only(top: 20),
                child: Text(
                  _result!.success ? 'Verification Successful!' : 'Verification Failed',
                  style: TextStyle(
                    color: _result!.success ? Colors.green : Colors.red,
                    fontSize: 16,
                  ),
                ),
              ),
  ],
        ),
      ),
    );
  }

  Future<void> _handleVerification() async {
    setState(() => _isLoading = true);
    
    try {
      await koraLivenessService.initialize(
        publicKey: 'your_public_key',
        livenessOptions: KoraLivenessOptions(
          debugMode: true,
          sandboxEnvironment: true,
        ),
      );
      
      final component = await koraLivenessService.checkLiveness(
        KoraLivenessOptionsExtended(
          user: LivenessUser(
            firstName: 'John',
            lastName: 'Doe',
            email: '[email protected]',
          ),
          onSuccess: (result) {
            setState(() {
              _result = result;
              _livenessComponent = null;
              _isLoading = false;
            });
          },
          onFailure: (result) {
            setState(() {
              _result = result;
              _livenessComponent = null;
 _isLoading = false;
            });
          },
          onClose: () {
            setState(() {
              _livenessComponent = null;
              _isLoading = false;
            });
          },
        ),
      );
      
      if (component != null && mounted) {
        setState(() {
          _livenessComponent = component as Widget;
          _isLoading = false;
        });
      }
    } catch (error) {
      setState(() => _isLoading = false);
      print('Verification error: $error');
    }
  }
}

Alternative: Widget-Based Integration

For direct widget embedding in your Flutter app:

class DirectWidgetVerification extends StatefulWidget {
  @override
  _DirectWidgetVerificationState createState() => _DirectWidgetVerificationState();
}
class _DirectWidgetVerificationState extends State<DirectWidgetVerification> {
  bool _showVerification = false;
  LivenessResult? _result;

  @override
  Widget build(BuildContext context) {
    if (_showVerification) {
      return LivenessCheck(
        props: LivenessCheckProps(
          publicKey: 'your_public_key_here',
          type: 'passive', // or 'active', or null for auto-detection
          config: KoraLivenessConfig(
            debugMode: true,
            sandboxEnvironment: true,
            user: LivenessUser(
              firstName: 'John',
              lastName: 'Doe',
              email: '[email protected]',
            ),
            branding: LivenessBranding(
              color: '#2376F3',
              logo: 'assets/images/logo.png',
              name: 'Your Company',
            ),
            presentation: 'page',
            allowAudio: true,
          ),
          onSuccess: (result) {
            setState(() {
              _result = result;
              _showVerification = false;
            });
          },
          onFailure: (result) {
					setState(() {
              _result = result;
              _showVerification = false;
            });
          },
          onClose: () {
            setState(() {
              _showVerification = false;
            });
          },
          loadingComponent: CircularProgressIndicator(),
          errorComponent: Text('Failed to load verification'),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(title: Text('Identity Verification')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _showVerification = true;
                });
              },
              child: Text('Start Verification'),
            ),
            if (_result != null)
              Padding(
                padding: EdgeInsets.only(top: 20),
                child: Text(
                  _result!.success ? 'Success!' : 'Failed',
                  style: TextStyle(
  color: _result!.success ? Colors.green : Colors.red,
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Flutter Configuration Classes

// Main configuration
class KoraLivenessConfig {
  final String? publicKey;
  final bool? debugMode;
  final bool? sandboxEnvironment;
  final LivenessUser? user;
  final LivenessBranding? branding;
  final String? presentation;
  final bool? allowAudio;
  final List<Map<String, dynamic>>? tasks;
  final void Function(LivenessResult)? onSuccess;
  final void Function(LivenessResult)? onFailure;
  final void Function()? onClose;
  final void Function()? onStart;
}

// User information
class LivenessUser {
  final String firstName;
  final String lastName;
  final String? email;
  final String? phone;
}

// Branding options
class LivenessBranding {
  final String? color;
  final String? logo;
  final String? name;
  final String? logoAlt;
  final bool? hideLogoOnMobile;
  final bool? showPoweredBy;
  final String? poweredByText;
  final String? poweredByLogo;
}

// Result object
class LivenessResult {
  final bool success;
  final dynamic data;
  final String? error;
  final Map<String, dynamic>? metadata;
}

Advanced Configuration for Active Liveness

Custom Task Configuration:

final customTasks = [
  {
    'id': 'yes_or_no',
    'difficulty': 'medium',
    'questions': [
      {
        'question': 'Are you ready to proceed?',
        'answer': true,
        'errorMessage': 'Please nod your head to the right for yes'
    }
    ]
  },
  {
    'id': 'motions',
    'difficulty': 'hard',
    'maxNods': 3,
    'maxBlinks': 2,
    'timeout': 45000
  }
];

Flutter-Specific Notes

  • Uses flutter_inappwebview for webview implementation ensuring consistent behavior across iOS and Android
  • Built-in permission handling for camera access
  • Supports both widget-based and service-based integration patterns
  • Comprehensive error handling and state management
  • Full type safety with Dart null safety support
  • Hot reload compatible for rapid development