azakaw_kyc_flutter 1.0.0
azakaw_kyc_flutter: ^1.0.0 copied to clipboard
Azakaw KYC onboarding for Flutter — runs the Azakaw compliance onboarding flow in a WebView, with region and environment selection matching the Azakaw web SDK.
example/lib/main.dart
import 'package:azakaw_kyc_flutter/azakaw_kyc_flutter.dart';
import 'package:flutter/material.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Azakaw KYC example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
/// Prefills the session id so testing does not depend on the simulator or
/// emulator sharing the host clipboard:
///
/// `flutter run --dart-define=SESSION_ID=<uuid>`
const _presetSessionId = String.fromEnvironment('SESSION_ID');
/// Preselects region/environment the same way, e.g. `--dart-define=REGION=kuwait`.
const _presetRegion = String.fromEnvironment('REGION');
const _presetEnv = String.fromEnvironment('ENV');
class _HomePageState extends State<HomePage> {
final _sessionIdController = TextEditingController(text: _presetSessionId);
AzakawRegion _region = _presetRegion.isEmpty
? AzakawRegion.uae
: AzakawRegion.values.byName(_presetRegion);
AzakawEnvironment _environment = _presetEnv.isEmpty
? AzakawEnvironment.sandbox
: AzakawEnvironment.values.byName(_presetEnv);
AzakawLanguage? _language;
bool _hideSidebar = false;
bool _hideOnboardingSections = false;
bool _isFullscreen = true;
bool _resolveTenantHost = true;
String? _outcome;
@override
void dispose() {
_sessionIdController.dispose();
super.dispose();
}
AzakawKycConfig? _buildConfig() {
try {
return AzakawKycConfig(
sessionId: _sessionIdController.text,
region: _region,
environment: _environment,
hideSidebar: _hideSidebar,
hideOnboardingSections: _hideOnboardingSections,
language: _language,
isFullscreen: _isFullscreen,
resolveTenantHost: _resolveTenantHost,
// Logs the postMessage traffic, which is how you confirm the bridge
// is alive: you should see an inbound IframeParametersRequest.
enableLogging: true,
);
} on ArgumentError catch (error) {
setState(() => _outcome = 'Invalid config: ${error.message}');
return null;
}
}
Future<void> _start() async {
final config = _buildConfig();
if (config == null) return;
setState(() => _outcome = null);
final result = await AzakawKyc.start(
context: context,
config: config,
onError: (error) => debugPrint('example: onError $error'),
onExternalUrl: (url) => debugPrint('example: external url $url'),
);
if (!mounted) return;
setState(() {
_outcome = switch (result.status) {
AzakawKycStatus.completed => 'Completed',
AzakawKycStatus.cancelled => 'Cancelled by the user',
AzakawKycStatus.failed => 'Failed: ${result.error?.message}',
};
});
}
@override
Widget build(BuildContext context) {
final urls = resolveAzakawUrls(region: _region, environment: _environment);
// Ask the SDK rather than re-deriving the rule: Kuwait has a sandbox,
// KSA and Qatar do not.
final unavailable = _region.subdomainFor(_environment) == null;
return Scaffold(
appBar: AppBar(title: const Text('Azakaw KYC example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
TextField(
controller: _sessionIdController,
decoration: const InputDecoration(
labelText: 'Session id',
helperText: 'Obtained from the Azakaw backend',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
DropdownButtonFormField<AzakawRegion>(
initialValue: _region,
decoration: const InputDecoration(
labelText: 'Region',
border: OutlineInputBorder(),
),
items: [
for (final region in AzakawRegion.values)
DropdownMenuItem(
value: region,
child: Text('${region.wireName} — ${region.domain}'),
),
],
onChanged: (value) => setState(() => _region = value!),
),
const SizedBox(height: 16),
DropdownButtonFormField<AzakawEnvironment>(
initialValue: _environment,
decoration: const InputDecoration(
labelText: 'Environment',
border: OutlineInputBorder(),
),
items: [
for (final environment in AzakawEnvironment.values)
DropdownMenuItem(
value: environment,
child: Text(environment.wireName),
),
],
onChanged: (value) => setState(() => _environment = value!),
),
const SizedBox(height: 16),
DropdownButtonFormField<AzakawLanguage?>(
initialValue: _language,
decoration: const InputDecoration(
labelText: 'Language',
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(value: null, child: Text("Portal's default")),
DropdownMenuItem(
value: AzakawLanguage.en,
child: Text('English'),
),
DropdownMenuItem(
value: AzakawLanguage.ar,
child: Text('العربية (RTL)'),
),
],
onChanged: (value) => setState(() => _language = value),
),
SwitchListTile(
title: const Text('Hide sidebar'),
subtitle: const Text('Delivered over the handshake'),
value: _hideSidebar,
onChanged: (value) => setState(() => _hideSidebar = value),
),
SwitchListTile(
title: const Text('Hide onboarding sections'),
subtitle: const Text('Start on the first form page'),
value: _hideOnboardingSections,
onChanged: (value) =>
setState(() => _hideOnboardingSections = value),
),
SwitchListTile(
title: const Text('Fullscreen'),
value: _isFullscreen,
onChanged: (value) => setState(() => _isFullscreen = value),
),
SwitchListTile(
title: const Text('Resolve tenant host'),
subtitle: const Text(
'Open on the tenant domain from the token, instead of '
'loading the generic host and redirecting',
),
value: _resolveTenantHost,
onChanged: (value) => setState(() => _resolveTenantHost = value),
),
const SizedBox(height: 8),
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Resolved hosts',
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 8),
SelectableText(
'base: ${urls.baseUrl}\n'
'api: ${urls.apiUrl}\n'
'auth: ${urls.authUrl}',
style: const TextStyle(fontFamily: 'monospace'),
),
if (unavailable) ...[
const SizedBox(height: 8),
Text(
'No ${_environment.wireName} host is provisioned for '
'${_region.wireName} — this will not resolve.',
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
],
],
),
),
),
const SizedBox(height: 16),
FilledButton(
onPressed: _start,
child: const Text('Start onboarding'),
),
if (_outcome != null) ...[
const SizedBox(height: 16),
Card(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: ListTile(
title: const Text('Result'),
subtitle: Text(_outcome!),
),
),
],
],
),
);
}
}