blusalt_liveness_native 2.1.5 copy "blusalt_liveness_native: ^2.1.5" to clipboard
blusalt_liveness_native: ^2.1.5 copied to clipboard

Liveness SDK for Android and IOS

example/lib/main.dart

import 'dart:io';

import 'package:blusalt_liveness_native/enums.dart';
import 'package:blusalt_liveness_native/liveness.dart';
import 'package:blusalt_liveness_native/model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:liveness_example/json_viewer_widget.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

enum VerificationCompletedType { comparison, detection, none }

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _livenessPlugin = BlusaltLivenessNative();

  String? imageUri;
  TextEditingController clientIdController =
      TextEditingController(text: '');
  TextEditingController appNameController =
      TextEditingController(text: '');
  TextEditingController apiKeyController =
      TextEditingController(text: '');
  TextEditingController webHookUrlController = TextEditingController();
  TextEditingController referenceController = TextEditingController();
  TextEditingController thresholdController = TextEditingController(text: '');
  TextEditingController timeoutDurationController = TextEditingController();
  bool isDev = false;

  bool isShowScore = false;
  bool isShowThreshold = false;
  bool startProcessOnGettingToFirstScreen = false;
  bool showLivenessResult = false;
  bool collectDeviceLocation = false;
  bool collectDeviceId = false;
  bool enableEncryption = false;
  ThresholdPriority thresholdPriority = ThresholdPriority.localOnly;

  LivenessFacialComparisonType facialComparisonType =
      LivenessFacialComparisonType.flash;
  LivenessDetectionOnlyType detectionOnlyType = LivenessDetectionOnlyType.flash;

  VerificationCompletedType verificationType = VerificationCompletedType.none;
  final ImagePicker _picker = ImagePicker();

  BlusaltLivenessResultResponse? resultResponse;

  @override
  void dispose() {
    super.dispose();
    clientIdController.dispose();
    appNameController.dispose();
    apiKeyController.dispose();
    webHookUrlController.dispose();
    referenceController.dispose();
    thresholdController.dispose();
    timeoutDurationController.dispose();
  }

  Future<BlusaltLivenessResultResponse?> startFaceComparison(
      bool isCamera) async {
    try {
      final XFile? image = await _picker.pickImage(
          source: isCamera ? ImageSource.camera : ImageSource.gallery,
          preferredCameraDevice: CameraDevice.front);
      if (image == null) return null;

      setState(() => imageUri = image.path);
      // await _setSdkContext('facial_comparison');

      resultResponse = await _livenessPlugin.startFacialComparisonSDK(
        apiKey: apiKeyController.text,
        appName: appNameController.text,
        clientId: clientIdController.text,
        isDev: isDev,
        webhookUrl: webHookUrlController.text.isEmpty
            ? null
            : webHookUrlController.text,
        reference: referenceController.text,
        imageData: await image.readAsBytes(),
        livenessFacialComparisonType: facialComparisonType,
        startProcessOnGettingToFirstScreen: startProcessOnGettingToFirstScreen,
        showLivenessResult: showLivenessResult,
        collectDeviceLocation: collectDeviceLocation,
        collectDeviceId: collectDeviceId,
        thresholdPriority: thresholdPriority,
        showScore: isShowScore,
        showThreshold: isShowThreshold,
        enableEncryption: enableEncryption,
        thresholdInPercent: validateNumber(thresholdController.text),
        timeoutDurationInSec:
            validateDurationNumber(timeoutDurationController.text),
      );

      if (resultResponse?.blusaltLivenessProcess ==
          BlusaltLivenessProcess.completed) {
        setState(() => verificationType = VerificationCompletedType.comparison);
      } else {
        // Non-completed result is not a crash, but log it for visibility.
        // await FirebaseCrashlytics.instance.log(
        //   'FacialComparison non-completed: '
        //   'code=${resultResponse?.code} '
        //   'msg=${resultResponse?.message}',
        // );\
        debugPrint("Prod");
        debugPrint(resultResponse?.code ?? '');
        debugPrint(resultResponse?.message ?? '');
      }
      return resultResponse;
    } on PlatformException catch (e, stack) {
      // Plugin threw a native-side exception (permission denied, camera failure, etc.)
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   stack,
      //   fatal: false,
      //   reason: 'PlatformException during facial comparison',
      //   information: ['code: ${e.code}', 'message: ${e.message}'],
      // );
      debugPrint(
          '[PlatformException] facial_comparison: ${e.code} – ${e.message}');
      return null;
    } catch (e, stack) {
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   stack,
      //   fatal: false,
      //   reason: 'Unexpected error during facial comparison',
      // );
      debugPrint('[ERROR] facial_comparison: $e');
      return null;
    }
  }

  Future<BlusaltLivenessResultResponse?> startLivenessDetectionOnly() async {
    try {
      // await _setSdkContext('liveness_detection');

      resultResponse = await _livenessPlugin.startLivenessDetectionOnlySDK(
        apiKey: apiKeyController.text,
        appName: appNameController.text,
        clientId: clientIdController.text,
        isDev: isDev,
        livenessDetectionOnlyType: detectionOnlyType,
        webhookUrl: webHookUrlController.text.isEmpty
            ? null
            : webHookUrlController.text,
        reference: referenceController.text,
        startProcessOnGettingToFirstScreen: startProcessOnGettingToFirstScreen,
        showLivenessResult: showLivenessResult,
        collectDeviceLocation: collectDeviceLocation,
        collectDeviceId: collectDeviceId,
        enableEncryption: enableEncryption,
        timeoutDurationInSec:
            validateDurationNumber(timeoutDurationController.text),
      );

      if (resultResponse?.blusaltLivenessProcess ==
          BlusaltLivenessProcess.completed) {
        setState(() => verificationType = VerificationCompletedType.detection);
      } else {
        debugPrint(resultResponse?.code ?? '');
        debugPrint(resultResponse?.message ?? '');
      }
      return resultResponse;
    } on PlatformException catch (e, stack) {
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   stack,
      //   fatal: false,
      //   reason: 'PlatformException during liveness detection',
      //   information: ['code: ${e.code}', 'message: ${e.message}'],
      // );
      debugPrint(
          '[PlatformException] liveness_detection: ${e.code} – ${e.message}');
      return null;
    } catch (e, stack) {
      // await FirebaseCrashlytics.instance.recordError(
      //   e,
      //   stack,
      //   fatal: false,
      //   reason: 'Unexpected error during liveness detection',
      // );
      debugPrint('[ERROR] liveness_detection: $e');
      return null;
    }
  }

  double? validateNumber(String value) {
    if (value.isEmpty) return null;
    return double.tryParse(value);
  }

  int? validateDurationNumber(String value) {
    if (value.isEmpty) return null;
    return int.tryParse(value);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              if (imageUri != null)
                Image.file(
                  File(imageUri!),
                  width: 100,
                  height: 100,
                )
              else
                const Padding(
                  padding: EdgeInsets.only(top: 20),
                  child: Text("No image selected"),
                ),
              const SizedBox(height: 14),
              TextField(
                controller: clientIdController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter client id",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                onChanged: (text) => setState(() {}),
              ),
              const SizedBox(height: 14),
              TextField(
                controller: appNameController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter app name",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                onChanged: (text) => setState(() {}),
              ),
              const SizedBox(height: 14),
              TextField(
                controller: apiKeyController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter api key",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                onChanged: (text) => setState(() {}),
              ),
              const SizedBox(height: 14),
              TextField(
                controller: webHookUrlController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter webhook url (Optional)",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                onChanged: (text) => setState(() {}),
              ),
              const SizedBox(height: 14),
              TextField(
                controller: referenceController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: "Enter reference (Optional)",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                onChanged: (text) => setState(() {}),
              ),
              const SizedBox(height: 14),
              TextField(
                controller: timeoutDurationController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText:
                      "Enter a duration you want SDK to timeout in seconds (Optional)",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                keyboardType: TextInputType.number,
                inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                maxLines: 2,
              ),
              const SizedBox(height: 4),
              CustomCheckbox(
                label: "Point to Development Environment?",
                value: isDev,
                onCheck: (value) => setState(() => isDev = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Start Process On Getting To First Screen?",
                value: startProcessOnGettingToFirstScreen,
                onCheck: (value) =>
                    setState(() => startProcessOnGettingToFirstScreen = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Show Liveness Result Screen",
                value: showLivenessResult,
                onCheck: (value) => setState(() => showLivenessResult = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Collect Device Location?",
                value: collectDeviceLocation,
                onCheck: (value) =>
                    setState(() => collectDeviceLocation = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Collect Device Id?",
                value: collectDeviceId,
                onCheck: (value) => setState(() => collectDeviceId = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Enable Encryption?",
                value: enableEncryption,
                onCheck: (value) => setState(() => enableEncryption = value),
              ),
              const SizedBox(height: 14),
              DropdownButtonFormField<LivenessDetectionOnlyType>(
                initialValue: detectionOnlyType,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: "Liveness Detection Type",
                ),
                items: LivenessDetectionOnlyType.values.map((type) {
                  return DropdownMenuItem(
                    value: type,
                    child: Text(type.name.toUpperCase()),
                  );
                }).toList(),
                onChanged: (value) {
                  if (value != null) setState(() => detectionOnlyType = value);
                },
              ),
              const SizedBox(height: 34),
              const Text(
                'More Options Specific to Facial Comparison:',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Show Percent on completion",
                value: isShowScore,
                onCheck: (value) => setState(() => isShowScore = value),
              ),
              const SizedBox(height: 2),
              CustomCheckbox(
                label: "Show Threshold on completion",
                value: isShowThreshold,
                onCheck: (value) => setState(() => isShowThreshold = value),
              ),
              const SizedBox(height: 14),
              DropdownButtonFormField<ThresholdPriority>(
                initialValue: thresholdPriority,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: "Threshold Priority",
                ),
                items: ThresholdPriority.values.map((priority) {
                  return DropdownMenuItem(
                    value: priority,
                    child: Text(priority.name.toUpperCase()),
                  );
                }).toList(),
                onChanged: (value) {
                  if (value != null) setState(() => thresholdPriority = value);
                },
              ),
              const SizedBox(height: 14),
              DropdownButtonFormField<LivenessFacialComparisonType>(
                initialValue: facialComparisonType,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: "Facial Comparison Type",
                ),
                items: LivenessFacialComparisonType.values.map((type) {
                  return DropdownMenuItem(
                    value: type,
                    child: Text(type.name.toUpperCase()),
                  );
                }).toList(),
                onChanged: (value) {
                  if (value != null)
                    setState(() => facialComparisonType = value);
                },
              ),
              const SizedBox(height: 14),
              TextField(
                controller: thresholdController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText:
                      "Enter a threshold number for facial comparison which ranges from 0-100 (Optional)",
                  hintStyle: TextStyle(color: Color(0xFFCCCCCC)),
                ),
                keyboardType: TextInputType.number,
                inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                maxLines: 2,
              ),
              const SizedBox(height: 24),
              Row(crossAxisAlignment:
                CrossAxisAlignment.start,
                children: [
                  Expanded(
                    child: ElevatedButton(
                      onPressed: startLivenessDetectionOnly,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        minimumSize: const Size(double.infinity, 50),
                      ),
                      child: const Text(
                        "Start SDK Liveness Detection",
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: Column(
                      children: [
                        ElevatedButton(
                          onPressed: () => startFaceComparison(false),
                          onLongPress: () => startFaceComparison(true),
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.blue,
                            minimumSize: const Size(double.infinity, 50),
                          ),
                          child: const Text(
                            "Start SDK Facial Comparison",
                            textAlign: TextAlign.center,
                            style: TextStyle(color: Colors.white),
                          ),
                        ),
                        const Text(
                          "Long Press to capture from camera",
                          textAlign: TextAlign.center,
                          style: TextStyle(color: Colors.grey),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              if (verificationType != VerificationCompletedType.none)
                const SizedBox(height: 34),
              if (verificationType != VerificationCompletedType.none)
                Text(
                  "Result: (${verificationType == VerificationCompletedType.comparison ? 'Facial Comparison' : 'Face Detection'})",
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontWeight: FontWeight.w500),
                ),
              if (verificationType != VerificationCompletedType.none)
                const SizedBox(height: 4),
              if (verificationType != VerificationCompletedType.none)
                // JsonViewerWidget(jsonData: resultResponse?.toJson()),
                Text(
                  'code: ${resultResponse?.code ?? ''}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontWeight: FontWeight.w500),
                ),
              if (verificationType != VerificationCompletedType.none)
                Text(
                  'isPassProcedureValidation: ${resultResponse?.isPassProcedureValidation ?? ''}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontWeight: FontWeight.w500),
                ),
              if (verificationType == VerificationCompletedType.comparison)
                Text(
                  'isPassFaceComparison: ${resultResponse?.comparisonData?.isPassFaceComparison ?? ''}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontWeight: FontWeight.w500),
                ),
              if (verificationType != VerificationCompletedType.none)
                Text(
                  'isPassFaceGenuiness: ${resultResponse?.faceDetectionData?.isPassFaceGenuiness ?? ''}',
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontWeight: FontWeight.w500),
                ),
              const SizedBox(height: 34),
              Image.asset(
                'assets/images/blusalt_logo.png',
                width: 100,
                height: 100,
                color: Colors.black,
              ),
              const SizedBox(height: 24),
            ],
          ),
        ),
      ),
    );
  }
}

class CustomCheckbox extends StatelessWidget {
  final String label;
  final bool value;
  final Function(bool) onCheck;

  const CustomCheckbox({
    Key? key,
    required this.label,
    required this.value,
    required this.onCheck,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Checkbox(
          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          value: value,
          onChanged: (newValue) => onCheck(newValue ?? false),
        ),
        Expanded(
          child: Text(
            label,
            style: const TextStyle(fontWeight: FontWeight.w500),
          ),
        ),
      ],
    );
  }
}
2
likes
130
points
597
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Liveness SDK for Android and IOS

Homepage

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_web_plugins

More

Packages that depend on blusalt_liveness_native

Packages that implement blusalt_liveness_native