skaletek_kyc 0.0.25 copy "skaletek_kyc: ^0.0.25" to clipboard
skaletek_kyc: ^0.0.25 copied to clipboard

PlatformiOS

A Flutter SDK for integrating Skaletek's eKYC verification services.

Skaletek KYC Flutter Plugin #

A comprehensive Flutter plugin for Know Your Customer (KYC) verification services, featuring document scanning, face liveness detection, and identity verification powered by AWS Amplify.

✨ Features #

  • πŸ†” Document Verification: Passport, National ID, Driver's License, and more
  • πŸ‘€ Face Liveness Detection: Real-time biometric verification using AWS Amplify
  • πŸ“Έ Camera Integration: Live document capture with auto-detection
  • 🎨 Customizable UI: Branded verification experience
  • πŸ”’ Secure: Enterprise-grade security with AWS infrastructure
  • πŸ“± Cross-platform: iOS and Android support

πŸš€ Quick Start #

1. Installation #

dependencies:
  skaletek_kyc: ^0.0.25
flutter pub get

2. Platform Setup #

πŸ“± Android Setup #

Requires Kotlin 2.2.0.

Step 1: Update Project Build Configuration #

1.1. Kotlin Version (Project Level)

Set Kotlin to 2.2.0 or higher. Check android/settings.gradle(.kts) for a plugins { } block containing version numbers:

  • Present β€” set the Kotlin version there. Projects created with recent Flutter versions already declare 2.3.20 and need no change.
  • Absent β€” add the buildscript block below to android/build.gradle.

Groovy β€” android/build.gradle

buildscript {
    ext.kotlin_version = '2.2.0'

    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:8.9.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

Kotlin DSL β€” android/build.gradle.kts

buildscript {
    extra.apply {
        set("kotlin_version", "2.2.0")
    }

    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath("com.android.tools.build:gradle:8.9.1")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
    }
}

1.2. Update android/app/build.gradle (App Level)

Add at the bottom of the file, after the android { } and flutter { } blocks.

Groovy β€” android/app/build.gradle

// Skaletek KYC setup
def flutterRoot = file("${rootProject.projectDir}/..")
def lockFile = file("${flutterRoot}/pubspec.lock")
def setupScript = null

if (lockFile.exists()) {
    def entry = (lockFile.text =~ /(?m)^  skaletek_kyc:\n((?:^ {4,}.*\n)+)/)
    if (entry.find()) {
        def details = entry.group(1)
        def pathMatch = (details =~ /path:\s*"?([^"\n]+)"?/)

        if (details.contains('source: path') && pathMatch.find()) {
            setupScript = file("${flutterRoot}/${pathMatch.group(1)}/android/skaletek_kyc.gradle")
        } else {
            def versionMatch = (details =~ /version:\s*"([^"]+)"/)
            if (versionMatch.find()) {
                def userHome = System.getProperty('user.home')
                def localAppData = System.getenv('LOCALAPPDATA') ?: "$userHome/AppData/Local"
                setupScript = ["$userHome/.pub-cache/hosted/pub.dev",
                               "$localAppData/Pub/Cache/hosted/pub.dev"]
                    .collect { file("$it/skaletek_kyc-${versionMatch.group(1)}/android/skaletek_kyc.gradle") }
                    .find { it.exists() }
            }
        }
    }
}

if (setupScript?.exists()) {
    apply from: setupScript
} else {
    logger.warn("Skaletek KYC: skaletek_kyc.gradle not found - run 'flutter pub get'")
}

Kotlin DSL β€” android/app/build.gradle.kts

// Skaletek KYC setup
val flutterRoot = file("${rootProject.projectDir}/..")
val lockFile = file("$flutterRoot/pubspec.lock")

val setupScript: File? = lockFile.takeIf { it.exists() }?.readText()?.let { lock ->
    val details = Regex("(?m)^  skaletek_kyc:\\n((?:^ {4,}.*\\n)+)")
        .find(lock)?.groupValues?.get(1)

    when {
        details == null -> null
        "source: path" in details ->
            Regex("path:\\s*\"?([^\"\\n]+)\"?").find(details)?.groupValues?.get(1)
                ?.let { file("$flutterRoot/$it/android/skaletek_kyc.gradle") }
        else -> Regex("version:\\s*\"([^\"]+)\"").find(details)?.groupValues?.get(1)?.let { version ->
            val userHome = System.getProperty("user.home")
            val localAppData = System.getenv("LOCALAPPDATA") ?: "$userHome/AppData/Local"
            listOf("$userHome/.pub-cache/hosted/pub.dev", "$localAppData/Pub/Cache/hosted/pub.dev")
                .map { file("$it/skaletek_kyc-$version/android/skaletek_kyc.gradle") }
                .find { it.exists() }
        }
    }
}?.takeIf { it.exists() }

if (setupScript != null) {
    apply(from = setupScript)
} else {
    logger.warn("Skaletek KYC: skaletek_kyc.gradle not found - run 'flutter pub get'")
}

Step 2: Permissions (Automatic) #

INTERNET, CAMERA and NFC are declared in the SDK's manifest and merge into your app. Nothing to add unless you customise manifest merging.

Step 3: Update MainActivity #

Ensure your MainActivity extends FlutterFragmentActivity:

// android/app/src/main/kotlin/com/yourpackage/yourapp/MainActivity.kt
package com.yourpackage.yourapp

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()

🍎 iOS Setup #

Step 1: Deployment Target #

Set the iOS deployment target to 14.0.

In Xcode: select the Runner target β†’ General β†’ Minimum Deployments β†’ iOS 14.0.

Or in code: set all three occurrences in ios/Runner.xcodeproj/project.pbxproj (Debug, Release, Profile):

IPHONEOS_DEPLOYMENT_TARGET = 14.0;

Step 2: Permissions #

Add to ios/Runner/Info.plist. iOS terminates the app without them:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access for document scanning and face verification.</string>
<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to read your passport chip for identity verification.</string>

Step 3: NFC Setup #

Pass enableNfc: false on [KYCCustomization](#kyccustomization) to use document upload only. When true (default), Passport and National ID flows show the NFC option on supported devices.

iOS (NFC)

NFCReaderUsageDescription is added in Step 2. The capability must be enabled once in Xcode:

  1. Open ios/Runner.xcworkspace in Xcode.
  2. Select the Runner target β†’ Signing & Capabilities tab.
  3. Click + Capability and add Near Field Communication Tag Reading.

This automatically creates (or updates) ios/Runner/Runner.entitlements:

<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>

Note: NFC capability requires an Apple Developer account and a real device β€” NFC is not available in the iOS Simulator.

3. Add ISO 7816 application identifiers (required for e-passport reading)

This step is critical β€” without it the NFC session can start but immediately time out without detecting the chip.

Add the following to ios/Runner/Info.plist (not the entitlements file):

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>A0000002471001</string>
    <string>A0000002472001</string>
    <string>00000000000000</string>
</array>

These are the standard ICAO 9303 Application Identifiers used by e-passports. Place them alongside NFCReaderUsageDescription in Info.plist.

NFC troubleshooting

  • "Session timeout" / chip not detected: Ensure com.apple.developer.nfc.readersession.iso7816.select-identifiers with the three AIDs is in Info.plist (not the entitlements file). This is the most common cause of NFC sessions opening but immediately failing.
  • "Failed to connect to NFC chip": Confirm Near Field Communication Tag Reading capability is added in Xcode under Signing & Capabilities, and that Runner.entitlements contains com.apple.developer.nfc.readersession.formats = [TAG].
  • Authentication failed after the chip is detected: Check that the document number, date of birth, and expiry date match the MRZ exactly. Wrong MRZ key fields can feel like NFC detection failure because the chip rejects BAC/PACE authentication.
  • Android detection is inconsistent: Remove thick or metal cases, place the document on a flat surface, and keep the phone still for up to 25 seconds while the reader finds the chip antenna.
  • NFC only works on physical devices β€” not supported in the iOS Simulator.

πŸ“– API Reference #

KYCUserInfo #

final userInfo = KYCUserInfo(
  firstName: "John",
  lastName: "Doe",
  documentType: DocumentType.passport.value,
  issuingCountry: "USA",
);

KYCCustomization #

final customization = KYCCustomization(
  docSrc: DocumentSource.camera.value,
  partnerName: "Your Company",
  logoUrl: "https://example.com/logo.png", // optional
  primaryColor: Colors.blue, // optional
  enableNfc: true, // optional; false = hide NFC, upload flow only
);

Document Types #

Type Description
DocumentType.passport International passport
DocumentType.nationalId National ID card
DocumentType.driverLicense Driver's license
DocumentType.residencePermit Residence permit
DocumentType.healthCard Health/medical card

Document Sources #

Source Description
DocumentSource.camera Live camera capture with auto-detection
DocumentSource.file File upload from device gallery

🌐 Environment Configuration #

You can now specify the environment for the KYC verification process. This controls which backend endpoints are used for the session.

Supported Environments

  • SkaletekEnvironment.dev
  • SkaletekEnvironment.prod
  • SkaletekEnvironment.sandbox

Usage

SkaletekKYC.instance.startVerification(
  context: context,
  token: "your-token-here",
  userInfo: userInfo,
  customization: customization,
  environment: SkaletekEnvironment.prod, // or .dev, .sandbox
  onResult: (result) {
    // Handle result
  },
);
  • If you do not specify the environment parameter, it defaults to SkaletekEnvironment.dev.

Note:

  • The environment parameter is available in the KYCConfig and is passed through the SDK automatically.
  • The correct API endpoints are selected internally based on the environment you choose.

Verification result (onResult) #

The callback receives a typed [KYCResult](lib/src/models/kyc_result.dart): success (bool), status (KYCStatus?), message and errorCode.

KYCStatus values: success, failure, awaitReview (manual review pending β€” handle separately from failure), cancelled, inProgress, pending, completed, reject.

onComplete, which receives a Map<String, dynamic>, still works but is deprecated and will be removed in 1.0.0.


Complete Example #

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

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Skaletek KYC Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF1261C1)),
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _isVerifying = false;
  KYCResult? _result;

  Future<void> _startVerification() async {
    setState(() {
      _isVerifying = true;
      _result = null;
    });

    await SkaletekKYC.instance.startVerification(
      context: context,
      // Create the session token on your own backend. Never ship your API key
      // inside the app.
      token: 'your-token-here',
      userInfo: KYCUserInfo(
        firstName: 'Whyte',
        lastName: 'Peter',
        documentType: DocumentType.passport.value,
        issuingCountry: 'USA',
      ),
      customization: KYCCustomization(
        docSrc: DocumentSource.file.value,
        partnerName: 'Your Company',
      ),
      environment: SkaletekEnvironment.dev,
      onResult: (result) => setState(() {
        _isVerifying = false;
        _result = result;
      }),
    );
  }

  /// `AWAIT_REVIEW` means the session finished but a person still has to approve
  /// it β€” treat it as its own outcome rather than a failure.
  bool get _isUnderReview => _result?.status == KYCStatus.awaitReview;

  Color get _resultColor {
    if (_result?.success ?? false) return Colors.green;
    return _isUnderReview ? Colors.amber.shade800 : Colors.red;
  }

  String get _resultText {
    final result = _result!;
    return [
      if (result.success)
        'Verification complete'
      else if (_isUnderReview)
        'Under review'
      else
        'Verification failed (${result.status?.value ?? 'unknown'})',
      if (result.message?.isNotEmpty ?? false) result.message!,
      if (result.errorCode?.isNotEmpty ?? false)
        'Error code: ${result.errorCode}',
    ].join('\n');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Skaletek KYC')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.verified_user, size: 80, color: Color(0xFF1261C1)),
            const SizedBox(height: 24),
            const Text(
              'Skaletek KYC SDK Demo',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 32),

            if (_isVerifying)
              const CircularProgressIndicator()
            else
              SizedBox(
                width: double.infinity,
                child: FilledButton(
                  onPressed: _startVerification,
                  child: const Text('Start Identity Verification'),
                ),
              ),

            if (_result != null) ...[
              const SizedBox(height: 24),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  border: Border.all(color: _resultColor),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  _resultText,
                  textAlign: TextAlign.center,
                  style: TextStyle(color: _resultColor),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

πŸ”§ Troubleshooting #

Common Issues #

Android build errors:

Error Cause Fix
This version of the Compose Compiler requires Kotlin version … Kotlin version differs from the one the liveness plugin pins Set kotlin_version to 2.2.0
The Kotlin Gradle plugin was loaded multiple times… Same cause, reported as a warning Align Kotlin versions across settings.gradle and build.gradle
Plugin request for plugin already on the classpath must not include a version Versions declared in both settings.gradle and a buildscript block Declare them in one place β€” see Step 1.1
⚠️ Skaletek KYC: amplifyconfiguration.json not found Setup script ran before flutter pub get Run flutter pub get, then rebuild

iOS build errors:

  • Verify the iOS deployment target is 14.0 or higher

Plugin "SmithyCodeGeneratorPlugin" from package "smithy-swift" must be enabled before it can be used means the Amplify Swift packages resolved above the pinned versions. In Xcode, File β†’ Packages β†’ Reset Package Caches, then rebuild.

Face liveness:

  • Verify camera permissions are granted
  • Check network connectivity for AWS services

NFC: see Step 3 for entitlements and provisioning.