dynamsoft_mrz_scanner_bundle_flutter 3.4.1300
dynamsoft_mrz_scanner_bundle_flutter: ^3.4.1300 copied to clipboard
The Dynamsoft MRZ Scanner Flutter SDK provides a wrapper for building MRZ scanning applications with Flutter SDK.
example/lib/main.dart
import 'dart:io';
import 'dart:typed_data';
import 'package:dynamsoft_mrz_scanner_bundle_flutter/dynamsoft_mrz_scanner_bundle_flutter.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gal/gal.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scan MRZ',
theme: ThemeData.dark().copyWith(
colorScheme: ColorScheme.dark(
primary: Colors.orange,
secondary: Colors.orange,
),
scaffoldBackgroundColor: const Color(0xFF1A1A1A),
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MRZData? _mrzData;
String _displayString = "";
bool _showResult = false;
int _selectedTab = 0;
Uint8List? _portraitBytes;
Uint8List? _mrzProcessedBytes;
Uint8List? _oppProcessedBytes;
Uint8List? _mrzOriginalBytes;
Uint8List? _oppOriginalBytes;
final ScrollController _scrollController = ScrollController();
void _launchMrzScanner() async {
var config = MRZScannerConfig(
license: "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9",
);
MRZScanResult mrzScanResult = await MRZScanner.launch(config);
if (mrzScanResult.status == EnumResultStatus.finished &&
mrzScanResult.mrzData != null) {
await _populateResult(mrzScanResult);
} else if (mrzScanResult.status == EnumResultStatus.canceled) {
setState(() {
_displayString = "Scan canceled";
_showResult = false;
});
} else {
setState(() {
_displayString =
"ErrorCode: ${mrzScanResult.errorCode}\n\nErrorString: ${mrzScanResult.errorMessage}";
_showResult = false;
});
}
}
Future<void> _populateResult(MRZScanResult result) async {
final data = result.mrzData!;
final portraitBytes = result.portraitImage;
final mrzProcBytes = result.mrzSideDocumentImage;
final oppProcBytes = result.oppositeSideDocumentImage;
final mrzOrigBytes = result.mrzSideOriginalImage;
final oppOrigBytes = result.oppositeSideOriginalImage;
if (!mounted) return;
setState(() {
_mrzData = data;
_portraitBytes = portraitBytes;
_mrzProcessedBytes = mrzProcBytes;
_oppProcessedBytes = oppProcBytes;
_mrzOriginalBytes = mrzOrigBytes;
_oppOriginalBytes = oppOrigBytes;
_selectedTab = 0;
_showResult = true;
});
// Scroll to top
if (_scrollController.hasClients) {
_scrollController.jumpTo(0);
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: _showResult && _mrzData != null
? _buildResultContent()
: Center(
child: Text(
_displayString,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _launchMrzScanner,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text("Scan MRZ", style: TextStyle(fontSize: 16)),
),
),
),
],
),
),
);
}
Widget _buildResultContent() {
final data = _mrzData!;
final sex = data.sex.isNotEmpty
? data.sex.substring(0, 1).toUpperCase() + data.sex.substring(1)
: '';
final docTypeText = switch (data.documentType) {
'MRTD_TD1_ID' => 'ID (TD1)',
'MRTD_TD2_ID' => 'ID (TD2)',
'MRTD_TD3_PASSPORT' => 'Passport (TD3)',
_ => data.documentType,
};
return SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: name + portrait
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${data.firstName} ${data.lastName}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'$sex, ${data.age} years old',
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(height: 4),
Text(
'Expiry: ${data.dateOfExpire}',
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
),
GestureDetector(
onLongPress: _portraitBytes == null
? null
: () => _showSaveDialog(_portraitBytes!),
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(8),
),
clipBehavior: Clip.antiAlias,
child: _portraitBytes != null
? FittedBox(
fit: BoxFit.cover,
child: Image.memory(_portraitBytes!),
)
: Image.asset(
'assets/images/portrait_placeholder.jpg',
fit: BoxFit.cover,
),
),
),
],
),
const SizedBox(height: 24),
// Tabs: Processed / Original
_buildTabs(),
const SizedBox(height: 12),
_buildTabImages(),
const SizedBox(height: 24),
// Personal Info
const Text(
'Personal Info',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
_infoRow('Given Name', data.firstName),
_infoRow('Surname', data.lastName),
_infoRow('Date of Birth', data.dateOfBirth),
_infoRow('Gender', sex),
_infoRow('Nationality', data.nationalityRaw),
const SizedBox(height: 20),
// Document Info
const Text(
'Document Info',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
_infoRow('Doc. Type', docTypeText),
_infoRow('Doc. Number', data.documentNumber),
_infoRow('Expiry Date', data.dateOfExpire),
const SizedBox(height: 20),
// Raw MRZ Text
const Text(
'Raw MRZ Text',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text(
data.mrzText,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 13,
color: Color(0xFFCCCCCC),
),
),
],
),
);
}
Widget _buildTabs() {
return Row(
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _selectedTab = 0),
child: Column(
children: [
Text(
'Processed',
style: TextStyle(
color: _selectedTab == 0 ? Colors.white : Colors.grey,
fontWeight: _selectedTab == 0
? FontWeight.bold
: FontWeight.normal,
),
),
const SizedBox(height: 10),
Container(
height: 2,
color: _selectedTab == 0 ? Colors.white : Colors.transparent,
),
],
),
),
),
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _selectedTab = 1),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Column(
children: [
Text(
'Original',
style: TextStyle(
color: _selectedTab == 1 ? Colors.white : Colors.grey,
fontWeight: _selectedTab == 1
? FontWeight.bold
: FontWeight.normal,
),
),
const SizedBox(height: 10),
Container(
height: 2,
color: _selectedTab == 1
? Colors.white
: Colors.transparent,
),
],
),
),
),
),
],
);
}
Widget _buildTabImages() {
final img1 = _selectedTab == 0 ? _mrzProcessedBytes : _mrzOriginalBytes;
final img2 = _selectedTab == 0 ? _oppProcessedBytes : _oppOriginalBytes;
final hasFirst = img1 != null;
final hasSecond = img2 != null;
if (hasFirst && hasSecond) {
return SizedBox(
height: 140,
child: Row(
children: [
Expanded(child: _buildImageCard(img1)),
const SizedBox(width: 12),
Expanded(child: _buildImageCard(img2)),
],
),
);
} else if (hasFirst || hasSecond) {
return SizedBox(
height: 200,
child: Center(child: _buildImageCard(hasFirst ? img1 : img2)),
);
} else {
return const SizedBox.shrink();
}
}
Future<void> _saveImageToGallery(Uint8List bytes) async {
try {
final granted = await Gal.requestAccess();
if (!granted) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Photo library access denied')),
);
}
return;
}
final dir = await Directory.systemTemp.createTemp('mrz_img');
final file = File(
'${dir.path}/MRZ_${DateTime.now().millisecondsSinceEpoch}.jpg',
);
await file.writeAsBytes(bytes);
await Gal.putImage(file.path);
await file.delete();
await dir.delete();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Image saved to photo library')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Failed to save image: $e')));
}
}
}
Widget _buildImageCard(Uint8List? bytes) {
final card = Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.antiAlias,
child: bytes != null
? Image.memory(bytes, fit: BoxFit.contain)
: const Center(
child: Icon(Icons.image, size: 36, color: Colors.grey),
),
);
if (bytes == null) return card;
return GestureDetector(
onLongPress: () => _showSaveDialog(bytes),
child: card,
);
}
void _showSaveDialog(Uint8List bytes) {
showCupertinoDialog(
context: context,
builder: (ctx) => CupertinoAlertDialog(
title: const Text('Save Image'),
content: const Text('Save this image to photo library?'),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(ctx),
child: const Text('Cancel'),
),
CupertinoDialogAction(
onPressed: () {
Navigator.pop(ctx);
_saveImageToGallery(bytes);
},
child: const Text('Save'),
),
],
),
);
}
Widget _infoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
label,
style: const TextStyle(color: Color(0xFFAAAAAA), fontSize: 14),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
],
),
);
}
}