skaletek_kyc 0.0.25
skaletek_kyc: ^0.0.25 copied to clipboard
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
buildscriptblock below toandroid/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:
- Open
ios/Runner.xcworkspacein Xcode. - Select the Runner target β Signing & Capabilities tab.
- 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-identifierswith the three AIDs is inInfo.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.entitlementscontainscom.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.devSkaletekEnvironment.prodSkaletekEnvironment.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
environmentparameter, it defaults toSkaletekEnvironment.dev.
Note:
- The environment parameter is available in the
KYCConfigand 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.