Skip to content

Commit 9da6774

Browse files
[FSSDK-12503] fix: guard static MethodChannel from multi-engine overwrite (#103)
* fix: guard static MethodChannel from multi-engine overwrite (Android) 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> * fix: guard logger channel from multi-engine overwrite (Android) 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> * fix: guard static MethodChannel from multi-engine overwrite (iOS) 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> * fix: guard logger channel from multi-engine overwrite (iOS) 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> * refactor: split setChannel into setChannel/clearChannel for logger channels 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> * fix: add iOS engine detach cleanup and enforce private channel access 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> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 305b5ec commit 9da6774

4 files changed

Lines changed: 32 additions & 7 deletions

File tree

android/src/main/java/com/optimizely/optimizely_flutter_sdk/FlutterLogbackAppender.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@
1414
public class FlutterLogbackAppender extends AppenderBase<ILoggingEvent> {
1515

1616
public static final String CHANNEL_NAME = "optimizely_flutter_sdk_logger";
17-
public static MethodChannel channel;
17+
private static MethodChannel channel;
1818
private static final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
1919

20-
public static void setChannel(MethodChannel channel) {
21-
FlutterLogbackAppender.channel = channel;
20+
public static void setChannel(MethodChannel newChannel) {
21+
if (channel == null) {
22+
channel = newChannel;
23+
}
24+
}
25+
26+
public static void clearChannel() {
27+
channel = null;
2228
}
2329

2430
@Override

android/src/main/java/com/optimizely/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
212212

213213
@Override
214214
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
215+
if (channel != null) {
216+
return;
217+
}
215218
channel = new MethodChannel(binding.getBinaryMessenger(), "optimizely_flutter_sdk");
216219
channel.setMethodCallHandler(this);
217220
context = binding.getApplicationContext();
@@ -232,15 +235,16 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
232235
@Override
233236
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
234237
channel.setMethodCallHandler(null);
238+
channel = null;
235239
// Stop and detach the appender
236240
if (flutterLogbackAppender != null) {
237241
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
238242
rootLogger.detachAppender(flutterLogbackAppender);
239243
flutterLogbackAppender.stop();
240244
flutterLogbackAppender = null;
241245
}
242-
// Clean up the channel
243-
FlutterLogbackAppender.setChannel(null);
246+
// Clean up the logger channel
247+
FlutterLogbackAppender.clearChannel();
244248
}
245249

246250
@Override

ios/Classes/OptimizelyFlutterLogger.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ public class OptimizelyFlutterLogger: NSObject, OPTLogger {
1313
}
1414

1515
public static func setChannel(_ channel: FlutterMethodChannel) {
16-
loggerChannel = channel
16+
if loggerChannel == nil {
17+
loggerChannel = channel
18+
}
19+
}
20+
21+
public static func clearChannel() {
22+
loggerChannel = nil
1723
}
1824

1925
public func log(level: OptimizelyLogLevel, message: String) {

ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin {
3838

3939
/// Registers optimizely_flutter_sdk channel to communicate with the flutter sdk to receive requests and send responses
4040
public static func register(with registrar: FlutterPluginRegistrar) {
41+
if channel != nil {
42+
return
43+
}
4144
channel = FlutterMethodChannel(name: "optimizely_flutter_sdk", binaryMessenger: registrar.messenger())
4245
let instance = SwiftOptimizelyFlutterSdkPlugin()
4346
registrar.addMethodCallDelegate(instance, channel: channel)
@@ -50,7 +53,13 @@ public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin {
5053
taskQueue: taskQueue)
5154
OptimizelyFlutterLogger.setChannel(loggerChannel)
5255
}
53-
56+
57+
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
58+
Self.channel?.setMethodCallHandler(nil)
59+
Self.channel = nil
60+
OptimizelyFlutterLogger.clearChannel()
61+
}
62+
5463
/// Part of FlutterPlugin protocol to handle communication with flutter sdk.
5564
/// All method handlers receive a main-thread-safe result callback so that
5665
/// any handler calling result() from a background thread (e.g. async SDK

0 commit comments

Comments
 (0)