PCSC sample in Flutter/Dart
Here is a new PC/SC sample in Dart language I promised in PC/SC sample in different languages.
Fabien Rousseau wrote a PC/SC wrapper for Dart (the language) and Flutter (the UI toolkit).
The library is at
https://pub.dev/packages/flutter_pcsc. It is available for GNU/Linux, macOS and Windows.
The source code is at
https://github.com/fabienrousseau/flutter_pcsc and uses the MIT license. The current version is 0.0.4.
Fabien also provides an sample code at https://github.com/fabienrousseau/flutter_pcsc/blob/main/flutter_pcsc/example/lib/main.dart. I reused this code for my example bellow.
Installation
I just cloned the flutter_pcsc git repository.
You also need to install the flutter SDK to build and run the application. But if you use Flutter I imagine your already have the SDK installed.
Source code
You can use flutter create test
to create a sample application with the correct structure and configuration files.
Then edit the pubspec.yaml file to add in the dependencies:
section something like:
dependencies: flutter_pcsc: path: /home/rousseau/Documents/github/flutter_pcsc/flutter_pcsc
Of course you adapt the path to the location of your flutter_pcsc directory.
The source code of the sample application lib/main.dart is:
import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:convert'; import 'package:flutter_pcsc/flutter_pcsc.dart'; void main() { MyApp? myApp; runZonedGuarded(() async { WidgetsFlutterBinding.ensureInitialized(); FlutterError.onError = (FlutterErrorDetails details) { FlutterError.dumpErrorToConsole(details); myApp?.addError(details.toString()); }; runApp(myApp = MyApp()); }, (Object error, StackTrace stack) { myApp?.addError(error.toString()); }); } class MyApp extends StatelessWidget { final GlobalKey<_MyAppBodyState> _myAppKey = GlobalKey(); MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('PCSC example app'), ), body: MyAppBody(key: _myAppKey)), ); } void addError(String msg) { _myAppKey.currentState?.messages.add(Message.error(msg)); } } class MyAppBody extends StatefulWidget { const MyAppBody({required Key key}) : super(key: key); @override _MyAppBodyState createState() { return _MyAppBodyState(); } } enum MessageType { info, error } class Message { final String content; final MessageType type; Message(this.type, this.content); static info(String content) { return Message(MessageType.info, content); } static error(String content) { return Message(MessageType.error, content); } } class _MyAppBodyState extends State<MyAppBody> { static const List<int> selectAppletCommand = [ 0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01 ]; static const List<int> appCommand = [ 0x00, 0x00, 0x00, 0x00 ]; final ScrollController _scrollController = ScrollController(); final List<Message> messages = []; @override void initState() { super.initState(); helloWorld(); } Future<void> helloWorld() async { /* establish PCSC context */ int ctx = await Pcsc.establishContext(PcscSCope.user); CardStruct? card; try { /* get the reader list */ List<String> readers = await Pcsc.listReaders(ctx); if (readers.isEmpty) { messages.add(Message.error('Could not detect any reader')); } else { /* use the first reader */ String reader = readers[0]; setState(() { messages.add(Message.info('Using reader: $reader')); }); /* connect to the card */ card = await Pcsc.cardConnect( ctx, reader, PcscShare.shared, PcscProtocol.any); /* send select applet APDU */ var response = await Pcsc.transmit(card, selectAppletCommand); messages.add(Message.info('Card returned: ${hexDump(response)}')); /* send applet test command */ response = await Pcsc.transmit(card, appCommand); var sw = response.sublist(response.length - 2); var bytes = response.sublist(0, response.length - 2); var text = utf8.decode(bytes); messages.add(Message.info('Card returned: ${hexDump(response)}')); if (sw[0] != 0x90 || sw[1] != 0x00) { setState(() { messages .add(Message.error('Card returned an error: ${hexDump(sw)}')); }); } else { setState(() { messages.add(Message.info('text is: ${text}')); }); } } } finally { if (card != null) { try { /* disconnect from the card */ await Pcsc.cardDisconnect(card.hCard, PcscDisposition.resetCard); } on Exception catch (e) { messages.add(Message.error(e.toString())); } } try { /* release PCSC context */ await Pcsc.releaseContext(ctx); } on Exception catch (e) { messages.add(Message.error(e.toString())); } } } static String hexDump(List<int> csn) { return csn .map((i) => i.toRadixString(16).padLeft(2, '0').toUpperCase()) .join(' '); } _scrollToBottom() { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } @override Widget build(BuildContext context) { TextStyle errorStyle = const TextStyle(color: Colors.red); WidgetsBinding.instance?.addPostFrameCallback((_) => _scrollToBottom()); return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column(children: [ Expanded( child: ListView( controller: _scrollController, children: messages .map((e) => Text(e.content, style: e.type == MessageType.error ? errorStyle : null)) .toList())), Container( margin: const EdgeInsets.all(10), child: ElevatedButton( onPressed: () async { await tryAgain(); }, child: const Text("Try again"))) ])) ]); } tryAgain() async { messages.clear(); await helloWorld(); } }
Output
$ flutter run hanging current working directory to: /home/rousseau/Documents/flutter/blog Launching lib/main.dart on Linux in debug mode... Building Linux application... Syncing files to device Linux... 55ms Flutter run key commands. r Hot reload. 🔥🔥🔥 R Hot restart. h List all available interactive commands. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). 💪 Running with sound null safety 💪 An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:34019/ORqpNzBQLEQ=/ The Flutter DevTools debugger and profiler on Linux is available at: http://127.0.0.1:9100?uri=http://127.0.0.1:34019/ORqpNzBQLEQ=/
Flutter is a framework to create graphical applications. So when you run the sample code you get a new window.
Remarks
The code is long because it is a complete Flutter application so you have to handle the graphical interface.
I asked Fabien for a Dart only interface but flutter_pcsc depends on some Flutter mechanisms to handle asynchronism. So it is not possible to write a PC/SC application for the console just using Dart. But if you use Dart I guess you also use flutter.
Despite of his (nice) name Fabien Rousseau is not part of my direct family. So I do not have a potential conflict of interest here 😜.
Conclusion
Thanks to Fabien for telling about his Dart/Flutter project.
If you work on a Free Software PC/SC wrapper that is not yet in my list please let me know.