TL;DR;
nfc_managerに破壊的変更が入ったことで、インターネットにある書き方と、公式ドキュメントの書き方が変わってしまっています。
これが問題なので、まずは読み取りにおける手法に関して、新しい書き方でまとめていきます。
公式ドキュメント
作ったもの
nfc_helper.dart
// Dart imports:
import 'dart:async';
// Package imports:
import 'package:logger/logger.dart';
import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager/nfc_manager_android.dart';
// Project imports:
import './nfc_data.dart';
import './repo_nfc.dart';
class ImplNfc implements RepoNfc {
ImplNfc({required NfcManager manager, required Logger logger})
: _manager = manager,
_logger = logger;
final NfcManager _manager;
final Logger _logger;
Completer<NfcData?> completer = Completer<NfcData?>();
@override
Future<NfcData?> read() async {
completer = Completer<NfcData?>();
if (await isAvailable) {
_logger.i('NFC is available');
await _manager.startSession(
pollingOptions: {NfcPollingOption.iso18092, NfcPollingOption.iso14443},
onDiscovered: (NfcTag tag) async {
try {
// Try NfcA first (most common)
final NfcAAndroid? nfcA = NfcAAndroid.from(tag);
if (nfcA != null) {
final idm = nfcA.tag.id.toString();
final nfcData = NfcData(type: NfcType.nfcA, idm: idm);
if (!completer.isCompleted) completer.complete(nfcData);
await _manager.stopSession();
return;
}
// Try NfcB as fallback
final NfcBAndroid? nfcB = NfcBAndroid.from(tag);
if (nfcB != null) {
final idm = nfcB.tag.id.toString();
final nfcData = NfcData(type: NfcType.nfcB, idm: idm);
if (!completer.isCompleted) completer.complete(nfcData);
await _manager.stopSession();
return;
}
// Try NfcF as last resort
final NfcFAndroid? nfcF = NfcFAndroid.from(tag);
if (nfcF != null) {
_logger.i('NfcF tag detected');
final idm = nfcF.tag.id.toString();
final nfcData = NfcData(type: NfcType.nfcF, idm: idm);
if (!completer.isCompleted) completer.complete(nfcData);
await _manager.stopSession();
return;
}
throw Exception('Unsupported tag type');
} catch (e, st) {
_logger.e(
'Error occurred while reading NFC tag',
error: e,
stackTrace: st,
);
if (!completer.isCompleted) completer.complete(null);
await _manager.stopSession();
}
},
);
} else {
_logger.i('NFC is not available');
completer.complete(null);
}
return completer.future;
}
@override
Future<bool> get isAvailable async {
return await _manager.checkAvailability() == NfcAvailability.enabled;
}
@override
Future<void> cancel() async {
await _manager.stopSession();
completer.complete(null);
}
}
主に変わったところ
- NfcAなどの種類において、AndoroidとIOSで使用型が変わった
- Ndefなどを利用したい場合は、
nfc_manager_ndefを別途importしないといけなくなった -
NfcManager.instanceにisAvailableがなくなった- 代わりに、
Availabilityメソッドが追加された
- 代わりに、
- NfcTag自体のレコードの扱い方がガッツリ変わった
- idmなどを直接取得できなくなった(Unit8List型になった)
NFC Managerの注意点
-
NfcManager.instance自体はシングルトンではないため、別途状態管理する必要がある -
NfcManager.instance.startSession()はNative側のI/Oポートを開く処理をするだけなので、この処理は読み込む処理自体ではない- そのため、startSessionの直後に、見つかった後の処理を書いてはいけない(特に
stopSessionを含む処理)
- そのため、startSessionの直後に、見つかった後の処理を書いてはいけない(特に
- また、Helper関数を作る際は、
completerを使うべき- これは、startSessionがIO開くだけで、見つかるまで待ってくれないので、Completerで強制的に完了を待たせるようにするべき。
- すると使用感が、普通の非同期実行のread関数になる
あと、型の考え方として、
NfcTag -> NfcAAndoroidやNdefMessageなどの具体型
という流れがある。
NfcTagにはバイナリ形式のRecordが入っているだけなので、これを具体型に変換して使用するというのが基本的な流れ。
この変換はNullableで行われるため、変換後 Null になれば、Recordはその具体型ではない、みたいな分岐処理が書ける。
ただし、Nullable なので、Nullチェックを必ず挟まないといけない(コンパイルエラーになる)。
終わりに
備忘録なので、わかりにくかったらごめんなさい。
Completerの処理に迷っているので、他にいい方法がある方はぜひ、コメントください!
(具体的には、外部からのCancelやタイムアウト処理に迷っています)