Skip to content

[iOS] Stale 409 (user cancelled) from previous session's delegate fires into new session's poll callback #232

@SeeyouYsen

Description

@SeeyouYsen

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

  1. Start a poll() and successfully read a tag
  2. Call finish() — iOS NFC sheet is still visible
  3. Tap the Cancel button
  4. Immediately start a new poll() from the app
  5. The new poll() throws PlatformException(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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions