complycube 1.0.6
complycube: ^1.0.6 copied to clipboard
The official Flutter SDK for integrating ComplyCube's Identity Verification UI into your mobile app.
example/lib/main.dart
import 'dart:async';
import 'package:complycube/complycube.dart';
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const ComplyCubeExampleApp());
}
class ComplyCubeExampleApp extends StatelessWidget {
const ComplyCubeExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ComplyCube Flutter SDK Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0F766E)),
useMaterial3: true,
),
home: const ComplyCubeExamplePage(),
);
}
}
class ComplyCubeExamplePage extends StatefulWidget {
const ComplyCubeExamplePage({super.key});
@override
State<ComplyCubeExamplePage> createState() => _ComplyCubeExamplePageState();
}
class _ComplyCubeExamplePageState extends State<ComplyCubeExamplePage> {
StreamSubscription<ComplyCubeEvent>? _sdkEventsSub;
final List<String> _eventLog = <String>[];
bool _isLaunching = false;
@override
void initState() {
super.initState();
_appendLog('Subscribing to ComplyCube.events stream...');
_sdkEventsSub = ComplyCube.events.listen(
_handleSdkEvent,
onError: (Object error, StackTrace stackTrace) {
_appendLog('events stream error: $error');
},
);
}
@override
void dispose() {
_sdkEventsSub?.cancel();
super.dispose();
}
void _handleSdkEvent(ComplyCubeEvent event) {
if (event is ComplyCubeSuccess) {
_appendLog('onSuccess: payload=${event.payload}');
return;
}
if (event is ComplyCubeError) {
_appendLog(
'onError: code=${event.code}, message=${event.message}, details=${event.details}',
);
return;
}
if (event is ComplyCubeCancelled) {
_appendLog('onCancelled: code=${event.code}, reason=${event.reason}');
return;
}
if (event is ComplyCubeCustom) {
_appendLog('onCustomEvent: ${event.event}');
return;
}
_appendLog('onUnknownEvent: $event');
}
void _appendLog(String message) {
if (!mounted) {
return;
}
final String timestamp = DateTime.now().toIso8601String();
setState(() {
_eventLog.insert(0, '[$timestamp] $message');
});
}
Future<void> _launch(
String label,
Future<ComplyCubeEvent> Function() launcher,
) async {
if (_isLaunching) {
_appendLog('Skipped "$label": a launch is already in progress.');
return;
}
setState(() {
_isLaunching = true;
});
_appendLog('Launching flow: $label');
try {
final ComplyCubeEvent launchResult = await launcher();
_appendLog('launch call result: ${_describeEvent(launchResult)}');
} catch (error) {
_appendLog('launch call threw: $error');
} finally {
if (mounted) {
setState(() {
_isLaunching = false;
});
}
}
}
String _describeEvent(ComplyCubeEvent event) {
if (event is ComplyCubeSuccess) {
return 'ComplyCubeSuccess(payload=${event.payload})';
}
if (event is ComplyCubeError) {
return 'ComplyCubeError(code=${event.code}, message=${event.message})';
}
if (event is ComplyCubeCancelled) {
return 'ComplyCubeCancelled(code=${event.code}, reason=${event.reason})';
}
if (event is ComplyCubeCustom) {
return 'ComplyCubeCustom(event=${event.event})';
}
return event.runtimeType.toString();
}
Future<void> _launchWithStages() async {
await _launch(
'ComplyCube.start(stages config)',
() => ComplyCube.start(_stagesFlowSettings()),
);
}
Future<void> _launchWithWorkflowTemplate() async {
await _launch(
'ComplyCube.start(workflowTemplateId config)',
() => ComplyCube.start(_workflowTemplateSettings()),
);
}
Future<void> _launchWithPreview() async {
await _launch(
'ComplyCube.start(preview + workflowDefinition)',
() => ComplyCube.start(_previewFlowSettings()),
);
}
Map<String, dynamic> _baseSettings() {
return <String, dynamic>{
// Required credentials:
// Use your real credentials from a secure source at runtime.
// Do NOT hardcode production secrets in source code.
'clientID': '69662ca82b64c6000268004b',
'clientToken': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkIjoiTTJGa056VTNZamMzWTJKa1lqVTBZelUzTVRFd1pEZzVOV1UxTkdNNE0yWXlaVEZsWXpVMk1XWXhNalkwWWpFek1tTmlOalU0TlRnMU5HRXhOVGs0WW1JM1lUY3dZVGcwWlRVMFlUVTNOMkl3WldSbE9HWTRNREkwTnpVME5EVXlORFEzTldRek1HUmxOalV3TVdRd09UWm1ZekkwTUdJM01XVmxPVGd6WkRRek9EVTNZbU15WVdKaU56WTRNekJpT0RNd04yTmlZMlV3WlRsaE1EWmpabVZpT1RrNU5XTTBaVGN4TURjelptRmtNMlUwTmpZNE1UUXhOMkZoT1RjMk4yTmlORGMzT0dWbFpXUmlZV1U0T1dWbU16bGxaalJrWm1ZME5HWTBNalEyTUROaE5USTVNMlUzTnpobU9XVTJNams1WWpWbE5qRm1OakUxWVRoa1pXTm1NRFU1WkRNMU5HVmhaVFl3TWpRNE56WTBPR1psT0RJMllUY3hNek5qIiwiZW52aXJvbm1lbnQiOiJsaXZlIiwidXJscyI6eyJhcGkiOiJodHRwczovL2FwaS5jb21wbHljdWJlLmNvbSIsInN5bmMiOiJ3c3M6Ly94ZHMuY29tcGx5Y3ViZS5jb20iLCJjcm9zc0RldmljZSI6Imh0dHBzOi8veGQuY29tcGx5Y3ViZS5jb20ifSwib3B0aW9ucyI6eyJoaWRlQ29tcGx5Q3ViZUxvZ28iOmZhbHNlLCJlbmFibGVDdXN0b21Mb2dvIjp0cnVlLCJlbmFibGVUZXh0QnJhbmQiOnRydWUsImVuYWJsZUN1c3RvbUNhbGxiYWNrcyI6dHJ1ZSwiZW5hYmxlTmZjIjp0cnVlLCJpZGVudGl0eUNoZWNrTGl2ZW5lc3NBdHRlbXB0cyI6NSwiZG9jdW1lbnRJbmZsaWdodFRlc3RBdHRlbXB0cyI6MiwibmZjUmVhZEF0dGVtcHRzIjo1LCJlbmFibGVBZGRyZXNzQXV0b2NvbXBsZXRlIjp0cnVlLCJlbmFibGVXaGl0ZUxhYmVsaW5nIjpmYWxzZX0sImlhdCI6MTc3NjkzNDkxMywiZXhwIjoxNzc2OTM4NTEzfQ.KLXoUp6_LsygOSfwW3ax2S79e-cOqA6jbpEqnSl8kBo',
// Optional top-level settings:
'disableScreenCapture': true,
// Optional UI customization maps.
// Android parser expects `colorScheme`.
// iOS parser currently reads `scheme`.
// Including both keeps this example cross-platform.
'lookAndFeel': _lookAndFeelTemplate(),
'colorScheme': _colorSchemeTemplate(),
'scheme': _colorSchemeTemplate(),
// Optional iOS-only explicit country list support.
// When omitted, countries can still be inferred from stage config.
'countries': <String>['GB', 'FR', 'US', 'CA'],
};
}
Map<String, dynamic> _workflowTemplateSettings() {
final Map<String, dynamic> settings = _baseSettings();
settings['workflowTemplateId'] = 'YOUR_WORKFLOW_TEMPLATE_ID';
return settings;
}
Map<String, dynamic> _previewFlowSettings() {
final Map<String, dynamic> settings = _baseSettings();
settings['environment'] = ComplyCubeEnvironment.preview;
settings['workflowDefinition'] = <String, dynamic>{
'id': '696e57bb37ebc8000276325d',
'version': 1,
'status': 'inactive',
'workflowTemplateId': '694b9cbd9761050002908e24',
'workflowTemplateName': 'December Testing',
'workflowTemplateDescription': 'December',
'type': 'linear',
'useCaseType': 'standard',
'additionalVerificationRules': <Map<String, dynamic>>[
<String, dynamic>{'type': 'otp_verification', 'mode': 'none'},
<String, dynamic>{'type': 'autofill', 'mode': 'none'},
],
'tasks': <Map<String, dynamic>>[
<String, dynamic>{
'taskId': 'start',
'type': 'ui',
'subType': 'welcome_screen',
'isExplicit': false,
'defaultNextTask': 'consent',
},
<String, dynamic>{
'taskId': 'consent',
'type': 'ui',
'subType': 'consent_screen',
'isExplicit': false,
'defaultNextTask': 'task_10_97h_lp9',
},
<String, dynamic>{
'taskId': 'task_10_97h_lp9',
'type': 'ui',
'subType': 'document_capture_screen',
'implicitlyAddedForTasks': <String>['task_10_97h'],
'options': <String, dynamic>{
'showGuidance': true,
'enableMLAssistance': true,
'retryAttempts': 3,
'liveCaptureOnly': false,
'crossDeviceOnly': false,
'strictDocumentSelection': false,
'nfcCapture': false,
'documentTypes': <String, dynamic>{
'passport': <String, dynamic>{'enabled': true},
'residence_permit': <String, dynamic>{'enabled': true},
'driving_license': <String, dynamic>{'enabled': true},
'national_identity_card': <String, dynamic>{'enabled': true},
},
},
'isExplicit': false,
'defaultNextTask': 'task_10_97h',
},
<String, dynamic>{
'taskId': 'task_10_97h',
'type': 'check',
'subType': 'document_check',
'additionalParameters': <String, dynamic>{'action': 'create_check'},
'options': <String, dynamic>{
'minimumPermittedAge': 25,
'clientDataValidation': false,
'securityElementsThreshold': 50,
'livenessThreshold': 50,
'documentModelValidity': true,
},
'isExplicit': true,
'defaultNextTask': 'complete',
},
<String, dynamic>{
'taskId': 'complete',
'type': 'ui',
'subType': 'complete_screen',
'isExplicit': true,
'isFinalTask': true,
'defaultNextTask': 'complete',
},
],
'updatedAt': '2026-01-19T16:11:47.460Z',
'createdAt': '2026-01-19T16:11:39.899Z',
'description': 'Document check',
};
return settings;
}
Map<String, dynamic> _stagesFlowSettings() {
final Map<String, dynamic> settings = _baseSettings();
settings['stages'] = <Map<String, dynamic>>[
// Supported stage names (map-based): intro, consent, complete, outro,
// documentCapture, faceCapture/face_capture, poaCapture, addressCapture, customerInfo.
<String, dynamic>{
'name': 'intro',
'title': 'Welcome',
'message': 'We will now verify your identity.',
},
<String, dynamic>{
'name': 'consent',
'title': 'Consent',
'message': 'Please accept to continue verification.',
},
<String, dynamic>{
'name': 'documentCapture',
// documentCapture specific options:
'nfcEnabled': false,
'captureDocumentId': false,
'showGuidance': true,
'useLiveCaptureOnly': false,
'useMLAssistance': true,
'retryLimit': 3,
// `documentTypes` supports bool or country arrays.
'documentTypes': <String, dynamic>{
'passport': true,
'driving_license': <String>['GB', 'FR'],
'national_identity_card': <String>['GB', 'FR'],
'residence_permit': <String>['GB', 'FR'],
},
},
<String, dynamic>{
// Alias supported too: face_capture
'name': 'faceCapture',
'mode': 'photo',
'showGuidance': true,
'useLiveCaptureOnly': true,
'useMLAssistance': true,
'retryLimit': 2,
},
<String, dynamic>{
'name': 'poaCapture',
'showGuidance': true,
'useLiveCaptureOnly': true,
'useMLAssistance': true,
'retryLimit': 3,
'isAddressCaptureEnabled': true,
'documentTypes': <String, dynamic>{
'utility_bill': <String>['GB', 'FR'],
'bank_statement': true,
},
},
<String, dynamic>{
'name': 'customerInfo',
'title': 'Customer Information',
'customerInfoFields': <dynamic>[
// Built-in fields can be provided as strings.
'first_name',
'middle_name',
'last_name',
'date_of_birth',
// Complex details and metadata payload:
<String, dynamic>{
'details': <Map<String, dynamic>>[
<String, dynamic>{
'person': <dynamic>[
'nationality',
'birth_country',
'national_id',
<String, dynamic>{
'name': 'ssn',
'constraint': <String, dynamic>{
'expression': 'metadata.Has SSN contains yes',
},
},
],
'company': <dynamic>[
'name',
'website',
'registration_number',
],
},
],
'metadata': <Map<String, dynamic>>[
<String, dynamic>{
'key': 'Tax Residence',
'question': 'Are you a tax resident outside of your home country?',
'componentType': 'SINGLE_CHOICE',
'options': <Map<String, String>>[
<String, String>{'label': 'No', 'value': 'No'},
<String, String>{'label': 'Yes', 'value': 'Outside'},
],
'required': true,
'description': 'Select your tax residency status.',
},
<String, dynamic>{
'key': 'Jurisdiction Country',
'question': 'Select tax residence countries',
'componentType': 'MULTI_SELECT_COUNTRY',
// Country filtering for MULTI_SELECT_COUNTRY:
'countries': <String, dynamic>{
'mode': 'inclusion', // inclusion | exclusion
'list': <String>['US', 'CA', 'GB', 'FR'],
},
'constraint': <String, dynamic>{
'expression': 'metadata.Tax Residence contains Outside',
},
'required': true,
'description': 'All countries where you are tax resident.',
},
<String, dynamic>{
'key': 'Has SSN',
'question': 'Do you have a Social Security Number?',
'componentType': 'SINGLE_CHOICE',
'options': <Map<String, String>>[
<String, String>{'label': 'Yes', 'value': 'yes'},
<String, String>{'label': 'No', 'value': 'no'},
],
'constraint': <String, dynamic>{
'expression': 'metadata.Jurisdiction Country contains US',
},
'required': true,
'description': 'Required for US tax residents.',
},
<String, dynamic>{
'key': 'SSN Reason',
'question': 'Why do you not have an SSN?',
'componentType': 'PARAGRAPH',
'format': <String, String>{
'type': 'MAXCHAR',
'validation': '500',
},
'constraint': <String, dynamic>{
'expression': 'metadata.Has SSN contains no',
},
'required': false,
'description': 'Optional explanation.',
},
],
// Metadata templates for dynamic country-driven questions.
'metadataTemplates': <Map<String, dynamic>>[
<String, dynamic>{
'templateKey': 'TIN_HAS',
'question': 'Do you have a TIN for {country}?',
'componentType': 'SINGLE_CHOICE',
'options': <Map<String, String>>[
<String, String>{'label': 'Yes', 'value': 'yes'},
<String, String>{'label': 'No', 'value': 'no'},
],
'description': 'Select one option.',
},
<String, dynamic>{
'templateKey': 'TIN',
'question': 'Tax Identification Number for {country}',
'componentType': 'SHORT_ANSWER',
'format': <String, String>{
'type': 'MAXCHAR',
'validation': '100',
},
'description': 'Enter the TIN value.',
},
<String, dynamic>{
'templateKey': 'TIN_REASON',
'question': 'Reason for missing TIN in {country}',
'componentType': 'SINGLE_CHOICE',
'options': <Map<String, String>>[
<String, String>{'label': 'Pending', 'value': 'pending'},
<String, String>{'label': 'Not applicable', 'value': 'na'},
<String, String>{'label': 'Other', 'value': 'other'},
],
},
<String, dynamic>{
'templateKey': 'TIN_REASON_OTHER',
'question': 'Please explain why no TIN is available.',
'componentType': 'PARAGRAPH',
'format': <String, String>{
'type': 'MAXCHAR',
'validation': '500',
},
},
],
},
],
},
<String, dynamic>{
// Android parser supports complete/outro.
'name': 'complete',
'title': 'Verification Completed',
'message': 'Your verification flow is now complete.',
},
<String, dynamic>{
'name': 'outro',
'title': 'Done',
'message': 'You can now return to your app.',
},
];
return settings;
}
Map<String, dynamic> _lookAndFeelTemplate() {
return <String, dynamic>{
// General:
'isDarkMode': false,
'uiInterfaceStyle': 'inherited', // inherited | light | dark
'borderRadius': 8,
'enableAnimations': true,
// Primary button:
'primaryButtonColor': '#0F766E',
'primaryButtonBgColor': '#0F766E',
'primaryButtonPressedBgColor': '#115E59',
'primaryButtonTextColor': '#FFFFFF',
'primaryButtonBorderColor': '#0F766E',
// Secondary button:
'secondaryButtonColor': '#0F766E',
'secondaryButtonBgColor': '#FFFFFF',
'secondaryButtonPressedBgColor': '#E6FFFA',
'secondaryButtonTextColor': '#0F766E',
'secondaryButtonBorderColor': '#0F766E',
// Document selector (modern + legacy names):
'documentSelectorColor': '#FFFFFF',
'documentSelectorBorderColor': '#D1D5DB',
'documentSelectorIconColor': '#0F766E',
'documentSelectorTitleTextColor': '#111827',
'documentSelectorDescriptionTextColor': '#4B5563',
'documentTypeSelectorBgColor': '#FFFFFF',
'documentTypeSelectorBorderColor': '#D1D5DB',
'documentTypeSelectorIconColor': '#0F766E',
'documentTypeSelectorTitleTextColor': '#111827',
'documentTypeSelectorDescriptionTextColor': '#4B5563',
// Info popup/panel:
'infoPopupColor': '#F0FDFA',
'infoPopupIconColor': '#0F766E',
'infoPopupTitleTextColor': '#115E59',
'infoPopupDescriptionTextColor': '#0F172A',
'infoPanelColor': '#F0FDFA',
'infoPanelBgColor': '#F0FDFA',
'infoPanelIconColor': '#0F766E',
'infoPanelTitleColor': '#115E59',
'infoPanelTitleTextColor': '#115E59',
'infoPanelDescriptionTextColor': '#0F172A',
'popUpBgColor': '#F0FDFA',
'popUpTitleColor': '#115E59',
// Error popup/panel:
'errorPopupColor': '#FEF2F2',
'errorPopupIconColor': '#B91C1C',
'errorPopupTitleTextColor': '#991B1B',
'errorPopupDescriptionTextColor': '#7F1D1D',
'errorPanelColor': '#FEF2F2',
'errorPanelBgColor': '#FEF2F2',
'errorPanelIconColor': '#B91C1C',
'errorPanelTitleTextColor': '#991B1B',
'errorPanelDescriptionTextColor': '#7F1D1D',
// Camera and text:
'cameraButtonColor': '#0F766E',
'bodyTextColor': '#111827',
'headingTextColor': '#0F172A',
'subheadingTextColor': '#334155',
'linkButtonTextColor': '#0F766E',
'blueBigType': '#0F766E',
'textItemType': '#111827',
// Surfaces and input:
'backgroundColor': '#F8FAFC',
'backgroundContentColor': '#FFFFFF',
'backgroundContentContrastColor': '#F1F5F9',
'backgroundDividerColor': '#E2E8F0',
'editTextColor': '#111827',
};
}
Map<String, dynamic> _colorSchemeTemplate() {
return <String, dynamic>{
// Primary button:
'primaryButtonBgColor': '#0F766E',
'primaryButtonPressedBgColor': '#115E59',
'primaryButtonTextColor': '#FFFFFF',
'primaryButtonBorderColor': '#0F766E',
// Secondary button:
'secondaryButtonBgColor': '#FFFFFF',
'secondaryButtonPressedBgColor': '#E6FFFA',
'secondaryButtonTextColor': '#0F766E',
'secondaryButtonBorderColor': '#0F766E',
// Document selector aliases:
'docTypeBgColor': '#FFFFFF',
'docTypeTextColor': '#111827',
'docTypeBorderColor': '#D1D5DB',
'documentSelectorColor': '#FFFFFF',
'documentSelectorBorderColor': '#D1D5DB',
'documentSelectorIconColor': '#0F766E',
'documentSelectorTitleTextColor': '#111827',
'documentSelectorDescriptionTextColor': '#4B5563',
// Info popup aliases:
'popUpBgColor': '#F0FDFA',
'popUpTitleColor': '#115E59',
'infoPopupColor': '#F0FDFA',
'infoPopupIconColor': '#0F766E',
'infoPopupTitleTextColor': '#115E59',
'infoPopupDescriptionTextColor': '#0F172A',
// Error popup:
'errorPopupColor': '#FEF2F2',
'errorPopupIconColor': '#B91C1C',
'errorPopupTitleTextColor': '#991B1B',
'errorPopupDescriptionTextColor': '#7F1D1D',
// Text aliases:
'textSecondary': '#111827',
'headerTitle': '#0F172A',
'subheaderTitle': '#334155',
'linkButtonTextColor': '#0F766E',
'blueBigType': '#0F766E',
'textItemType': '#111827',
// Background/input:
'backgroundColor': '#F8FAFC',
'backgroundContentColor': '#FFFFFF',
'backgroundContentContrastColor': '#F1F5F9',
'backgroundDividerColor': '#E2E8F0',
'editTextColor': '#111827',
// Misc:
'cameraButtonColor': '#0F766E',
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ComplyCube Flutter SDK Example')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const Text(
'This example demonstrates recommended SDK launch patterns:\n'
'1) map-based staged flow, 2) workflowTemplateId flow, '
'3) preview + workflowDefinition.\n'
'Replace placeholder credentials before using in production.',
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _isLaunching ? null : _launchWithStages,
child: const Text('Launch with stages map'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _isLaunching ? null : _launchWithWorkflowTemplate,
child: const Text('Launch with workflowTemplateId'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _isLaunching ? null : _launchWithPreview,
child: const Text('Launch with preview + workflowDefinition'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: () => setState(_eventLog.clear),
child: const Text('Clear event log'),
),
const SizedBox(height: 12),
const Text(
'SDK Event Log',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(8),
),
child: _eventLog.isEmpty
? const Center(
child: Text('No events yet. Launch the SDK to begin.'),
)
: ListView.separated(
padding: const EdgeInsets.all(12),
itemCount: _eventLog.length,
separatorBuilder: (_, __) => const Divider(height: 16),
itemBuilder: (BuildContext context, int index) {
return Text(
_eventLog[index],
style: const TextStyle(fontFamily: 'monospace'),
);
},
),
),
),
],
),
),
),
);
}
}