nfc_util 2.2.0 copy "nfc_util: ^2.2.0" to clipboard
nfc_util: ^2.2.0 copied to clipboard

A Flutter plugin providing access to NFC features on Android and iOS.

2.2.0 #

The rest of the nfc_manager 4.x catch-up. Android-only additions; nothing is breaking.

Added #

  • NfcManager.onAdapterStateChanged, a Stream<NfcAdapterState> that emits when the user switches NFC on or off in system settings. Pairs with checkAvailability(): check once at startup, then react to changes instead of polling. The stream does not replay the current state. On iOS it never emits — there is no NFC toggle to watch.

    The receiver is registered against the application context, so a configuration change does not churn it, and it is unregistered on activity and engine detach. (Upstream registers an equivalent receiver and never unregisters it, leaking one per attach cycle.)

  • NfcBarcode tag class and the startSession(discoverNfcBarcode:) flag that makes it reachable. Barcode (Kovio) tags are only discovered when FLAG_READER_NFC_BARCODE is set, so the flag is the point — nfc_manager ships the tag class but never sets the flag from startSession, leaving the class unreachable through its own API. The class carries identifier, barcodeType and barcode, and has no operations, because android.nfc.tech.NfcBarcode has none.

  • startSession(skipNdefCheck:) sets FLAG_READER_SKIP_NDEF_CHECK, which makes discovery measurably faster by skipping the platform's NDEF probe. Ndef.from(tag) then returns null, so use it only when the tag's NDEF content does not interest you.

  • NfcManager.isSecureNfcSupported() / isSecureNfcEnabled(). Secure NFC restricts tag reading to an unlocked device. Android API 29+; both return false below that and on iOS.

  • MifareClassic.blockToSector() / sectorToBlock() / getBlockCountInSector(). Sector geometry is not uniform — a 4K card has 32 sectors of 4 blocks followed by 8 of 16 — so this arithmetic cannot be done by hand from blockCount and sectorCount. These resolve the technology without opening a connection, since they read a static description rather than talking to the tag. (Upstream implements all three natively but never exposed them in Dart, so they are unreachable there.)

2.1.0 #

Catches up with the useful parts of nfc_manager 4.x. Nothing here is breaking: existing code compiles and behaves as it did.

Added #

  • NfcManager.checkAvailability() returns NfcAvailability.enabled, .disabled or .unsupported. isAvailable() could only say "no", which conflated a device with no NFC hardware and a device where the user has switched NFC off — so an app could not tell whether pointing the user at system settings would help. Android reports all three; iOS never reports .disabled, because CoreNFC has no such state. isAvailable() still works but is deprecated and will be removed in 3.0.0.

  • NfcError.code carries the exact CoreNFC failure as a new NfcReaderErrorCode enum (24 CoreNFC codes plus unknown). The iOS side previously mapped 3 of CoreNFC's 22 and dropped the rest, so a lost tag, a disabled radio, a too-small tag and a security violation all arrived as NfcErrorType.unknown with nothing to tell them apart. NfcErrorType itself is unchanged — widening it would have broken exhaustive switch statements — so type stays the coarse category and code carries the detail. Null on Android, and null on iOS for failures that did not come from CoreNFC.

    The two codes added in the iOS 26 SDK (ineligible, accessNotAccepted) are matched by raw value, so the plugin still compiles against older SDKs. An unrecognised code decodes to NfcReaderErrorCode.unknown rather than throwing.

  • setTimeout / getTimeout / getMaxTransceiveLength on the Android tag classes. timeout and maxTransceiveLength are snapshots taken at discovery, so the timeout could be read but never changed — there was no way to give a slow tag more time before the exchange failed as tag_lost. setTimeout and getTimeout are on IsoDep, NfcA, NfcF, MifareClassic and MifareUltralight; getMaxTransceiveLength is on those plus NfcB and NfcV. android.nfc.tech offers no timeout accessor for NfcB and NfcV. The existing fields are untouched.

  • startSession(noPlatformSounds:) (Android). FLAG_READER_NO_PLATFORM_SOUNDS was applied unconditionally, so the system tag-discovery sound could never play and there was no way to ask for it. It is now a parameter. It defaults to true, matching what every release before this one did — note this is the opposite of nfc_manager, which defaults it to false. Pass noPlatformSounds: false to let the sound play.

  • Ndef.canMakeReadOnly (Android). The native side already reported this value; it was only reachable by digging through additionalData. It is now a proper field, and no longer appears in additionalData. Null on iOS, which does not report it. Check it before calling writeLock() — not every tag can be locked.

Fixed #

  • iOS: starting a session while one is already running is now rejected with a session_already_exists PlatformException. The second session used to overwrite the first, which was then never invalidated: its reader sheet stayed up and its tags leaked for the lifetime of the process.

2.0.0 #

Requirements #

  • Minimum Flutter is now 3.44.0. That is the first stable release which stages the FlutterFramework Swift package, which ios/nfc_util/Package.swift now depends on as the Flutter tool requires.
  • Minimum iOS deployment target is 15.6, declared by both the podspec and Package.swift.

Breaking #

  • Removed the unused flutter create boilerplate: NfcUtilPlatform, MethodChannelNfcUtil and the plugin_platform_interface dependency. These were never part of the NFC API.
  • Ndef.read() now returns Future<NdefMessage?>. A tag with no NDEF message previously crashed with a TypeError instead of reporting "nothing written yet".
  • MifareClassic.transceive takes Uint8List data instead of int data.
  • The Objective-C shim (NfcUtilPlugin.h/.m) is gone and the Swift class is now named NfcUtilPlugin instead of SwiftNfcUtilPlugin. Swift Package Manager does not support mixed-language targets, and the shim only existed to expose the Swift class to Objective-C. Nothing referenced these types from application code.

Fixed #

  • iOS builds work again under Swift Package Manager. Package.swift declared its sources at Classes/, which does not exist inside the package root, so the target was empty and xcodebuild refused to resolve it. Since Flutter 3.44 enables SPM by default, this failed every iOS build. The native sources now live at ios/nfc_util/Sources/nfc_util/, shared by both the podspec and Package.swift.

  • Android: implemented every tag I/O method. Ndef#read/write/writeLock, NfcA/NfcB/NfcF/NfcV/IsoDep#transceive, all nine MifareClassic commands, the three MifareUltralight commands and NdefFormatable#format/formatReadOnly previously threw MissingPluginException. Tag I/O runs on a dedicated thread and reuses the open connection, so a MifareClassic sector authentication still holds for the following readBlock.

  • iOS: tag I/O inside onDiscovered works again. 1.0.3 invalidated the session as soon as a tag was serialized, so Ndef.read()/write(), Iso7816.sendCommand() and friends failed with "Tag is not found". With invalidateAfterFirstRead: true the session now stays open until the app calls stopSession().

  • iOS: Nfc#stopSession no longer submits its Flutter reply twice when errorMessage is given (missing return), which also invalidated the session twice.

  • iOS: an unrecognized tag type left the reader sheet hanging until the 60 s timeout because the completion handler was never called.

  • iOS: starting a session with an unusable polling option reported success while no session had begun; it now returns an unavailable error.

  • iOS: the tag map is written from the CoreNFC delegate queue and read from the platform thread; access is now serialized.

  • FeliCa.requestSpecificationVersion() invoked FeliCa#requestSpecificationVersionResponse, which no platform implements, so it always threw MissingPluginException.

  • onDiscovered callbacks that throw no longer leak the native tag handle, and the error is no longer swallowed as an unhandled async error.

  • Decoding unknown NDEF type-name-format, MiFare family or error-type values from the platform threw StateError; these now fall back to their unknown variants. Records read off a tag also skip the creation-time format validation, which rejected legitimate chunked records.

  • Android: reader mode is now disabled and the tag map cleared on stopSession and on activity/engine detach. Reader mode previously stayed active, keeping the NFC radio polling and holding a reference to a destroyed activity.

  • Android: an empty or unrecognized pollingOptions list produced zero technology flags, which started a session that could never discover a tag. It now falls back to every supported technology.

Documentation #

  • Documented the most common iOS setup mistake, found while testing on a device: because startSession polls iso18092 by default, CoreNFC rejects the session with Missing required entitlement unless com.apple.developer.nfc.readersession.felica.systemcodes is in the app's Info.plist. The reader sheet simply never appears, and startSession still completes normally because iOS reports the failure asynchronously -- so an app without an onError callback sees nothing at all. The example now sets the key, passes onError on every session, and gained a "Tag I/O" button that exercises the tag commands on both platforms.

Changed #

  • Android: tag errors distinguish the connect step from the command itself (connect: TagLostException: ...), and failures are logged under the NfcUtilPlugin tag. A tag that answers discovery but cannot be connected is otherwise indistinguishable from a command that failed.
  • Android: dropped the leftover debug logging from startSession.
  • Android: reader mode sets FLAG_READER_NO_PLATFORM_SOUNDS and a 250 ms presence-check delay.
  • Platform value decoding uses constant reverse maps instead of a linear firstWhere scan per record.
  • Calls whose result is required now raise a PlatformException(code: 'no_result') instead of force-unwrapping; NfcManager.isAvailable() returns false rather than throwing.
  • iOS: dropped the @available(iOS 13.0, *) scaffolding, now that the deployment target is 15.6.
  • CocoaPods builds ship PrivacyInfo.xcprivacy, matching what Swift Package Manager builds already did.

1.0.3 #

  • iOS first scan bug fixed.

1.0.2 #

  • Swift Package Manager support

1.0.0 #

  • flutter: min 3.32.0

0.1.1 #

  • Nfc#disposeTag fixed.

0.1.0 #

  • Android and iOS are done.

0.0.1 #

  • TODO: Describe initial release.
2
likes
160
points
388
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin providing access to NFC features on Android and iOS.

Repository (GitHub)
View/report issues

Topics

#nfc #ndef #mifare #felica

License

MIT (license)

Dependencies

flutter

More

Packages that depend on nfc_util

Packages that implement nfc_util