Flutter身份认证插件flutter_libjeid的使用
Flutter身份认证插件flutter_libjeid的使用
LibJeID(日本电子身份证库)是一个用于智能手机访问公共IC卡(如个人编号卡、驾照和护照)的库。这些卡在日本非常流行。
特性
目前我们正在使用libjeid的免费版本,因此无需任何产品库密钥即可安装。付费版本正在开发中。
官方文档
Android版本功能列表
- 读取个人编号卡
- 个人编号卡的公共个人认证功能
- 读取居住卡和特别永久居民证书
- 验证居住卡和特别永久居民证书的真实性
- 读取驾照
- 驾照真实性验证
iOS版本功能列表
- 读取个人编号卡
- 读取居住卡和特别永久居民证书
- 验证居住卡和特别永久居民证书的真实性
- 读取驾照
- 驾照真实性验证
免费版和付费版的功能项
如何使用
前往示例项目查看如何使用该库的完整示例。
首先创建一个FlutterLibjeid
实例,并检查NFC是否可用,然后订阅事件:
final _scanner = FlutterLibjeid();
final isNfcAvailable = await _scanner.isAvailable();
if (!isNfcAvailable) {
// 显示错误信息
return;
}
final _subscription = _scanner.eventStream.listen((event) {
switch (event) {
case FlutterLibjeidEventScanning():
// 使用 .setMessage() 在扫描对话框中显示消息
_scanner.setMessage(message: 'Scanning...');
break;
case FlutterLibjeidEventConnecting():
_scanner.setMessage(message: 'Connecting...');
break;
case FlutterLibjeidEventParsing():
_scanner.setMessage(message: 'Parsing...');
break;
case FlutterLibjeidEventSuccess(data: FlutterLibjeidCardData data):
// 使用 .stopScan() 取消扫描过程
_scanner.stopScan();
_onSuccess(data);
break;
case FlutterLibjeidEventFailed(error: FlutterLibjeidError error):
_scanner.stopScan();
_onError(error);
break;
case FlutterLibjeidEventCancelled():
break;
}
});
然后处理数据或错误:
void _onSuccess(FlutterLibjeidCardData data) {
switch (data) {
case ResidentCardData():
// 对数据进行操作
break;
case MyNumberCardData():
// 对数据进行操作
break;
case DriverLicenseCardData():
// 对数据进行操作
break;
case PassportCardData():
// 对数据进行操作
break;
}
}
void _onError(FlutterLibjeidError error) {
switch (error) {
case NfcNotAvailableError():
// 对错误进行操作
break;
case NfcTagUnableToConnectError():
// 对错误进行操作
break;
case NfcCardBlockedError():
// 对错误进行操作
break;
case NfcCardTypeMismatchError():
// 对错误进行操作
break;
case InvalidMethodArgumentsError():
// 对错误进行操作
break;
case InvalidCardPinError(int remainingTimes):
// 对错误进行操作
break;
case InvalidCardKeyError():
// 对错误进行操作
break;
case UnknownError():
// 对错误进行操作
break;
}
}
接下来,调用您要使用的扫描方法:
await _scanner.scanResidentCard(cardNumber: 'xxxxxxxxxxxx');
await _scanner.scanMyNumberCard(pin: 'xxxx');
await _scanner.scanDriverLicenseCard(pin1: 'xxxx', pin2: 'xxxx');
await _scanner.scanPassportCard(cardNumber: 'xxxxxxxxxx', birthDate: 'xxxxxxxxxx', expiredDate: 'xxxxxxxxxx');
最后,不再需要时取消事件流订阅:
_subscription.cancel();
连接丢失问题
有几种原因可能导致标签连接丢失:
- iPhone对位置非常敏感,即使在读取过程中卡片稍微移动也可能导致“标签连接丢失”。
- 不同设备的可读性存在差异。我们还确认了一些设备几乎无法读取卡片。即使是同一型号,也可能有个体差异。
- 读取卡片时尽量保持卡片靠近标签,不要移动它。
问题已在官方仓库报告:https://github.com/osstech-jp/libjeid-ios-app/issues/1
贡献
欢迎为该项目贡献代码。
如果您发现了一个错误或希望实现一个功能,但不知道如何修复/实现,请填写一个问题。 如果您修复了一个错误或实现了某个功能,请发送一个拉取请求。
示例代码
import 'dart:convert';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_libjeid/flutter_libjeid.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
[@override](/user/override)
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Map<String, dynamic> _data = {};
String? _error;
final GlobalKey<ScaffoldState> _globalKey = GlobalKey();
final _flutterLibjeidPlugin = FlutterLibjeid();
final TextEditingController _rcCardNumberController = TextEditingController();
final TextEditingController _inCardPinController = TextEditingController();
final TextEditingController _dlCardPin1Controller = TextEditingController();
final TextEditingController _dlCardPin2Controller = TextEditingController();
final TextEditingController _epCardNumberController = TextEditingController();
final TextEditingController _epCardBirthDateController = TextEditingController();
final TextEditingController _epCardExpiredDateController = TextEditingController();
late final TabController _tabController;
late final StreamSubscription<FlutterLibjeidEvent> _eventStreamSubscription;
[@override](/user/override)
void initState() {
_tabController = TabController(length: 4, vsync: this);
_eventStreamSubscription = _flutterLibjeidPlugin.eventStream.listen(_onEventReceived);
scheduleMicrotask(() {
_flutterLibjeidPlugin.isAvailable().then((isAvailable) {
if (!isAvailable) {
setState(() => _error = 'NFC Not Available');
}
});
});
super.initState();
}
[@override](/user/override)
void dispose() {
_eventStreamSubscription.cancel();
super.dispose();
}
void _onEventReceived(FlutterLibjeidEvent event) {
switch (event) {
case FlutterLibjeidEventScanning():
_flutterLibjeidPlugin.setMessage(message: 'Scanning...');
setState(() {
_data = Map.from({});
_error = null;
});
break;
case FlutterLibjeidEventConnecting():
_flutterLibjeidPlugin.setMessage(message: 'Connecting...');
break;
case FlutterLibjeidEventParsing():
_flutterLibjeidPlugin.setMessage(message: 'Parsing...');
break;
case FlutterLibjeidEventSuccess(data: FlutterLibjeidCardData data):
_flutterLibjeidPlugin.stopScan();
_data = data.toJson();
FocusScope.of(context).unfocus();
_globalKey.currentState?.showBottomSheet(
(_) => BottomSheet(
onClosing: () {},
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.9,
),
builder: (_) => SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _data.entries
.map(
(e) => Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey[200]!),
),
),
child: Row(
children: [
Expanded(flex: 2, child: Text(e.key)),
Expanded(
flex: 3,
child: Builder(builder: (context) {
final value = e.value;
if (value is String && value.startsWith('data:image/')) {
return Image.memory(
const Base64Decoder().convert(
value.replaceFirst(
'data:image/png;base64,', ''),
),
);
}
return Text(e.value.toString());
}),
)
],
),
),
)
.toList(),
),
),
),
);
setState(() {});
break;
case FlutterLibjeidEventFailed(error: FlutterLibjeidError error):
_flutterLibjeidPlugin.stopScan();
setState(() => _error = error.toString());
break;
case FlutterLibjeidEventCancelled():
break;
}
}
Future<void> startScanResidentCard() async {
try {
await _flutterLibjeidPlugin.scanResidentCard(
cardNumber: _rcCardNumberController.text,
);
} catch (e) {
setState(() => _error = e.toString());
}
}
Future<void> scanMyNumberCard() async {
try {
await _flutterLibjeidPlugin.scanMyNumberCard(
pin: _inCardPinController.text,
);
} catch (e) {
setState(() => _error = e.toString());
}
}
Future<void> startScanDriverLicenseCard() async {
try {
await _flutterLibjeidPlugin.scanDriverLicenseCard(
pin1: _dlCardPin1Controller.text,
pin2: _dlCardPin2Controller.text,
);
} catch (e) {
setState(() => _error = e.toString());
}
}
Future<void> startScanPassportCard() async {
try {
await _flutterLibjeidPlugin.scanPassportCard(
cardNumber: _epCardNumberController.text,
birthDate: _epCardBirthDateController.text,
expiredDate: _epCardExpiredDateController.text,
);
} catch (e) {
setState(() => _error = e.toString());
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
key: _globalKey,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle.dark,
title: const Text('Libjeid Example'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text(
'该插件仍在开发中,可能会导致应用程序崩溃。保持冷静 ^_^',
style: TextStyle(
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 16),
TabBar(
controller: _tabController,
labelStyle: const TextStyle(fontSize: 16),
labelColor: Colors.blue,
isScrollable: true,
tabs: const [
SizedBox(height: 30, child: Text('我的编号 (IN)')),
SizedBox(height: 30, child: Text('驾驶执照 (DL)')),
SizedBox(height: 30, child: Text('居住卡 (RC)')),
SizedBox(height: 30, child: Text('护照 (EP)')),
],
onTap: (index) {
setState(() {
_data = Map.from({});
_error = null;
});
},
),
const SizedBox(height: 10),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
Column(
children: [
TextFormField(
controller: _inCardPinController,
maxLength: 4,
decoration: const InputDecoration(
hintText: '输入PIN',
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: scanMyNumberCard,
child: const Text('扫描我的编号卡'),
),
],
),
Column(
children: [
Row(
children: [
Expanded(
child: TextFormField(
controller: _dlCardPin1Controller,
maxLength: 4,
decoration: const InputDecoration(
hintText: '输入PIN 1',
),
),
),
const SizedBox(width: 12),
Expanded(
child: TextFormField(
controller: _dlCardPin2Controller,
maxLength: 4,
decoration: const InputDecoration(
hintText: '输入PIN 2',
),
),
)
],
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: startScanDriverLicenseCard,
child: const Text('扫描驾驶执照卡'),
),
],
),
Column(
children: [
TextFormField(
maxLength: 12,
controller: _rcCardNumberController,
decoration: const InputDecoration(
hintText: '输入卡片号码',
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: startScanResidentCard,
child: const Text('扫描居住卡'),
),
],
),
Column(
children: [
TextFormField(
maxLength: 12,
controller: _epCardNumberController,
decoration: const InputDecoration(
hintText: '输入卡片号码',
),
),
TextFormField(
maxLength: 12,
controller: _epCardBirthDateController,
decoration: const InputDecoration(
hintText: '输入出生日期 (YYMMDD)',
),
),
TextFormField(
maxLength: 12,
controller: _epCardExpiredDateController,
decoration: const InputDecoration(
hintText: '输入到期日期 (YYMMDD)',
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: startScanPassportCard,
child: const Text('扫描护照卡'),
),
],
)
],
),
),
const SizedBox(height: 20),
if (_error?.isNotEmpty ?? false)
Text(
'错误: $_error',
style: const TextStyle(color: Colors.red),
),
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _data.entries
.map(
(e) => Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey[200]!),
),
),
child: Row(
children: [
Expanded(flex: 2, child: Text(e.key)),
Expanded(
flex: 3,
child: Builder(builder: (context) {
final value = e.value;
if (value is String && value.startsWith('data:image/')) {
return Image.memory(
const Base64Decoder().convert(
value.replaceFirst(
'data:image/png;base64,', ''),
),
);
}
return Text(e.value.toString());
}),
)
],
),
),
)
.toList(),
),
),
),
],
),
),
),
),
);
}
}
更多关于Flutter身份认证插件flutter_libjeid的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter身份认证插件flutter_libjeid的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_libjeid
是一个用于读取日本身份证(My Number Card)信息的 Flutter 插件。它基于 libjeid
库,允许开发者从日本的身份证中读取个人信息、照片和其他数据。以下是如何在 Flutter 项目中使用 flutter_libjeid
插件的基本步骤。
1. 添加依赖
首先,在 pubspec.yaml
文件中添加 flutter_libjeid
插件的依赖:
dependencies:
flutter:
sdk: flutter
flutter_libjeid: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
以安装依赖。
2. 导入插件
在你的 Dart 文件中导入 flutter_libjeid
插件:
import 'package:flutter_libjeid/flutter_libjeid.dart';
3. 初始化插件
在使用插件之前,确保你已经初始化了插件。通常情况下,你可以在 main()
函数中进行初始化:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterLibjeid.initialize();
runApp(MyApp());
}
4. 读取身份证信息
使用 FlutterLibjeid
提供的 API 来读取身份证信息。你可以通过调用 readCard()
方法来读取身份证数据。
void readMyNumberCard() async {
try {
MyNumberCardData cardData = await FlutterLibjeid.readCard();
print('Name: ${cardData.name}');
print('Address: ${cardData.address}');
print('Photo: ${cardData.photo}');
} catch (e) {
print('Failed to read card: $e');
}
}
5. 处理读取到的数据
MyNumberCardData
类包含了从身份证中读取的所有信息。你可以根据需要处理这些数据。
class MyNumberCardData {
String name;
String address;
Uint8List photo;
// 其他字段...
MyNumberCardData({
required this.name,
required this.address,
required this.photo,
// 其他字段...
});
}
6. 显示照片
如果你想要在应用中显示从身份证中读取的照片,可以使用 Image.memory
组件:
Image.memory(cardData.photo);
7. 处理错误
在读取身份证信息的过程中,可能会遇到各种错误,例如设备不支持 NFC 或用户未授权访问。确保在你的代码中正确处理这些错误。
try {
MyNumberCardData cardData = await FlutterLibjeid.readCard();
// 处理数据
} on PlatformException catch (e) {
print('PlatformException: ${e.message}');
} catch (e) {
print('Unknown error: $e');
}
8. 请求权限
在读取身份证信息之前,你可能需要请求 NFC 权限。可以使用 permission_handler
插件来处理权限请求。
import 'package:permission_handler/permission_handler.dart';
void requestPermissions() async {
if (await Permission.nfc.request().isGranted) {
// 权限已授予
} else {
// 权限被拒绝
}
}
9. 注意事项
- 确保设备支持 NFC 功能。
- 用户需要在设备的设置中启用 NFC。
- 读取身份证信息可能需要用户的明确授权。
10. 示例代码
以下是一个完整的示例代码,展示了如何使用 flutter_libjeid
插件读取和显示身份证信息:
import 'package:flutter/material.dart';
import 'package:flutter_libjeid/flutter_libjeid.dart';
import 'dart:typed_data';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterLibjeid.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My Number Card Reader'),
),
body: Center(
child: MyNumberCardReader(),
),
),
);
}
}
class MyNumberCardReader extends StatefulWidget {
[@override](/user/override)
_MyNumberCardReaderState createState() => _MyNumberCardReaderState();
}
class _MyNumberCardReaderState extends State<MyNumberCardReader> {
MyNumberCardData? cardData;
void readCard() async {
try {
MyNumberCardData data = await FlutterLibjeid.readCard();
setState(() {
cardData = data;
});
} catch (e) {
print('Failed to read card: $e');
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (cardData != null)
Column(
children: [
Text('Name: ${cardData!.name}'),
Text('Address: ${cardData!.address}'),
Image.memory(cardData!.photo),
],
),
ElevatedButton(
onPressed: readCard,
child: Text('Read Card'),
),
],
);
}
}