Skip to content

fix(flutter): make forceStateUpdate emit state; fix iOS state-listener leak (FR-25944)#141

Closed
dianaKhortiuk-frontegg wants to merge 1 commit into
masterfrom
fix/flutter-force-state-update
Closed

fix(flutter): make forceStateUpdate emit state; fix iOS state-listener leak (FR-25944)#141
dianaKhortiuk-frontegg wants to merge 1 commit into
masterfrom
fix/flutter-force-state-update

Conversation

@dianaKhortiuk-frontegg

Copy link
Copy Markdown
Collaborator

FR-25944 — forceStateUpdate broken + iOS listener leak

forceStateUpdate never delivered state

  • Android: forceStateUpdate was a comment-only no-op that called result.success(null) without ever notifying the state listener. Added forceNotifyChanges() to the FronteggStateListener interface and routed forceStateUpdate through an extracted top-level forceStateUpdate(listener, result) (top-level so it's unit-testable without a Context).
  • iOS: there was no "forceStateUpdate" case in the method handler, so it fell through to FlutterMethodNotImplementedMissingPluginException in Dart. Added the case + a handler that triggers a one-off state emit through the listener, which is now wired into FronteggMethodCallHandler in register().

iOS Combine sink leak

  • FronteggStateListenerImpl.subscribe() appended sinks to cancellables without clearing prior ones. Every EventChannel onListen (e.g. Flutter hot restart / re-listen) leaked the previous subscriptions and fired updateState() once per accumulated sink. Now calls dispose() at the start of subscribe().
  • forceNotifyChanges() dispatches to a background queue before updateState()sendState() uses DispatchQueue.main.sync, and the force path runs on the main-thread method channel, so a direct call would deadlock. This mirrors how the existing Combine sinks already dispatch updateState().

Tests

  • ForceStateUpdateTest (Android): with a listener → forceNotifyChanges() fires and the result completes; with no listener → the result still completes. RED → GREEN verified via :frontegg_flutter:testDebugUnitTest.

Note

iOS changes pass swiftc -parse and are consistent with the existing state-listener/threading patterns, but were not compiled in a full iOS build here (no iOS Flutter build harness). Please let CI / an iOS build confirm before merge.

… (FR-25944)

forceStateUpdate() never delivered a refreshed state to Flutter, and on iOS it
threw MissingPluginException because the method had no handler case.

- Android: forceStateUpdate was a comment-only no-op that completed the result
  without notifying the state listener. Add forceNotifyChanges() to the
  FronteggStateListener interface and route forceStateUpdate through an extracted,
  unit-tested top-level forceStateUpdate(listener, result) that calls it.
- iOS: add the missing "forceStateUpdate" case and a handler that triggers a one-off
  state emit via the listener (wired into FronteggMethodCallHandler in register()).
- iOS: FronteggStateListenerImpl.subscribe() appended Combine sinks to cancellables
  without clearing prior ones, so every EventChannel onListen (e.g. hot restart)
  leaked sinks and fired updateState() once per accumulated sink. Call dispose()
  at the start of subscribe(). forceNotifyChanges() hops to a background queue
  before updateState() to avoid the DispatchQueue.main.sync deadlock when invoked
  from the main-thread method channel.

Test: ForceStateUpdateTest (Android, RED->GREEN).
@dianaKhortiuk-frontegg

Copy link
Copy Markdown
Collaborator Author

Superseded by #142, which combines FR-25941/42/43/44 into a single PR. Branch kept.

@github-actions github-actions Bot mentioned this pull request Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant