-
Notifications
You must be signed in to change notification settings - Fork 158
Description
Bug Description
On iOS, when a poll() call succeeds and finish() is called immediately afterward, the
tagReaderSession(_:didInvalidateWithError:) delegate callback for the old session may fire
with readerSessionInvalidationErrorUserCanceled (error code 409) after the next poll()
has already registered its FlutterResult callback.
Because the delegate ignores the session parameter (written as _:), there is no check to
confirm the callback belongs to the current session. This causes the stale 409 to be delivered
to the new poll(), making it look like the user cancelled a session they never interacted with.
Root Cause
In FlutterNfcKitPlugin.swift:
public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) {
guard result != nil else { return }
// ↑ only checks if a result callback exists, not which session it belongs to
...
result?(FlutterError(code: "409", ...))
}Race condition timeline:
T+0.000 finish() called → session.invalidate(), self.session = nil, self.result = nil
T+1.674 new poll() called → self.result = newCallback, session.begin()
T+2.698 old session's delegate fires (user tapped cancel on lingering NFC sheet)
→ guard self.result != nil → TRUE (it's the NEW callback!)
→ delivers 409 to the new poll()
Reproduction Steps
- Start a
poll()and successfully read a tag - Call
finish()— iOS NFC sheet is still visible - Tap the Cancel button
- Immediately start a new
poll()from the app - The new
poll()throwsPlatformException(409, SessionCanceled, ...)instantly, without
any user interaction on the new session
Expected Behavior
Each session's delegate callback should only affect that session's result. The stale 409 from
closing the previous session should not be delivered to a newly started poll().
Environment
- flutter_nfc_kit: 3.6.2
- iOS: iPhone 13 , ios 18.5
- Flutter: 3.41.4