[FSSDK-12503] fix: guard static MethodChannel from multi-engine overwrite#103
Merged
muzahidul-opti merged 6 commits intomasterfrom Apr 29, 2026
Merged
[FSSDK-12503] fix: guard static MethodChannel from multi-engine overwrite#103muzahidul-opti merged 6 commits intomasterfrom
muzahidul-opti merged 6 commits intomasterfrom
Conversation
When FirebaseMessaging.onBackgroundMessage (or any mechanism that creates a second FlutterEngine) triggers onAttachedToEngine a second time, the static channel was unconditionally overwritten. This caused notification callbacks to route to the wrong engine, silently dropping them. The fix adds an early return if channel is already set, and nulls the channel in onDetachedFromEngine so re-attachment works after a real detach. Fixes FSSDK-12503 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same root cause as the main channel fix — the static logger channel could be overwritten by a second FlutterEngine. The setChannel guard allows explicit null (cleanup) but prevents overwrite of an active channel. Fixes FSSDK-12503 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same vulnerability as Android — if register(with:) were called twice (e.g. by a second FlutterEngine), the static channel would be overwritten. The fix adds an early return if channel is already set. Fixes FSSDK-12503 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same pattern as the Android logger fix — prevents a second engine from overwriting the active logger channel. Fixes FSSDK-12503 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…annels Replace the compound null-guard logic with explicit setChannel (only sets if no active channel) and clearChannel (always clears). This matches the main plugin's pattern and makes intent immediately obvious. Callers updated: onDetachedFromEngine now calls clearChannel() instead of setChannel(null). Fixes FSSDK-12503 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pvcraven
previously approved these changes
Apr 29, 2026
pvcraven
left a comment
There was a problem hiding this comment.
This looks ok by my read, and the claude feedback doesn't look like there are any show-stoppers. You might want to double check #1 though:
PR Review: [FSSDK-12503] Guard static MethodChannel from multi-engine overwrite
Overall: Good fix for a real problem, with one notable gap on iOS.
What it does right
The root cause is well-identified: when a second FlutterEngine is created (e.g. by Firebase background messaging),
onAttachedToEngine / register(with:) is called again, unconditionally overwriting the static MethodChannel. This
silently breaks notification callbacks. The guard pattern (check channel != null before assigning) is the standard
Flutter approach for this.
The setChannel / clearChannel split on the loggers is clean — makes intent obvious and prevents accidental null
overwrites through the setter.
Issues
1. iOS has no onDetachedFromEngine equivalent — channel is never cleaned up (medium severity)
On Android, onDetachedFromEngine now nulls the channel and calls FlutterLogbackAppender.clearChannel(), so a real
detach-then-reattach cycle works correctly.
On iOS, there is no corresponding cleanup. SwiftOptimizelyFlutterSdkPlugin has no detach method, and
OptimizelyFlutterLogger.clearChannel() is added but never called from anywhere. If the original engine is destroyed,
channel stays non-null pointing at a dead messenger, and the early-return guard will prevent a legitimate new engine
from attaching.
This may not be a practical problem today (Flutter iOS doesn't typically create/destroy multiple engines the same
way), but it's an asymmetry worth either fixing or documenting.
2. FlutterLogbackAppender.channel is still public static (low severity)
Line 17 of FlutterLogbackAppender.java — the field is public static MethodChannel channel. The PR adds proper
setChannel / clearChannel methods, but anyone can still bypass them and write directly to the field. Consider making
it private static so the guard is enforceable.
3. Thread safety on the guard check (low severity)
Both Android and iOS check-then-assign without synchronization:
if (channel == null) {
channel = newChannel;
}
On Android, onAttachedToEngine is called on the main thread, so this is safe in practice. On iOS, register(with:) is
also main-thread. Worth confirming there's no codepath that calls these from a background thread, but this is likely
fine.
Nits
- The OptimizelyFlutterLogger.clearChannel() method on iOS is dead code (never called). Either wire it up in a plugin
detach path or remove it to avoid confusion.
Summary
The fix correctly addresses the multi-engine overwrite bug for the common case. The main gap is that iOS lacks a
cleanup path — if the first engine is destroyed, the SDK will be stuck with a dead channel reference that the guard
prevents from being replaced. I'd suggest either adding an iOS detach handler or documenting why it's safe to skip.
Add detachFromEngine(for:) to iOS plugin so channel is cleaned up when the engine detaches, matching Android's onDetachedFromEngine behavior. Make FlutterLogbackAppender.channel private to enforce use of setChannel/clearChannel accessors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
Author
Resolved |
pvcraven
approved these changes
Apr 29, 2026
pvcraven
left a comment
There was a problem hiding this comment.
Ok, this now adds the cleanup.
It'd be quicker if instead of just saying "resolved" you tell me what you resolved so I don't have to comb through change-history.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
onAttachedToEngine(Android) /register(with:)(iOS) is called more than onceMethodChannelwas unconditionally overwritten by a second Flutter engine (e.g. Firebase background messaging), causing notification callbacks to route to the wrong engineonDetachedFromEnginefor proper lifecycleChanges
OptimizelyFlutterSdkPlugin.java: Early return inonAttachedToEngineif channel already set; setchannel = nullinonDetachedFromEngineFlutterLogbackAppender.java: GuardsetChannel()from overwriting active channelSwiftOptimizelyFlutterSdkPlugin.swift: Early return inregister(with:)if channel already setOptimizelyFlutterLogger.swift: GuardsetChannel()from overwriting active channelTest plan
flutter test) - 140/140 passed, no regressionsflutter analyze- no issues foundFixes FSSDK-12503
🤖 Generated with Claude Code