I did a lot of debugging to find this, with the help of Copilot. So I also let Copilot write up this summary of my hour long debugging journey and the findings. In case anything is unclear, let me know!
Description
Using dotlottie_flutter: ^0.1.7 with firebase_messaging on Android can cause DotLottie method calls to fail with MissingPluginException.
The problem occurs when Firebase Messaging creates its background FlutterEngine. That engine registers dotlottie_flutter too, which overwrites the BinaryMessenger stored by the plugin for the main UI engine.
The DotLottie platform view can still be created successfully, but method-channel calls to it are then routed through the background engine's messenger and fail.
Environment
- Flutter:
3.44.6
- Dart:
3.12.2
- Android
dotlottie_flutter: 0.1.7
firebase_messaging: 16.3.0
- Main activity:
FlutterFragmentActivity
Steps to reproduce
- Create a Flutter Android app using
dotlottie_flutter: ^0.1.7.
- Add and configure
firebase_messaging, including a background message handler.
- Display a
DotLottieView in the main Flutter engine.
- Call a DotLottie method, for example:
await controller.stateMachineSetBooleanInput(
'isLightMode',
true,
);
Expected behavior
DotLottie method calls from the UI Flutter engine continue working after Firebase Messaging creates and initializes its background engine.
Actual behavior
A call such as stateMachineSetBooleanInput fails:
MissingPluginException(
No implementation found for method stateMachineSetBooleanInput
on channel dotlottie_view_0
)
The native libraries load and the platform view is created, but subsequent calls use the wrong Flutter engine messenger.
Root cause
The Android plugin stores its BinaryMessenger in a shared static field:
companion object {
lateinit var binaryMessenger: BinaryMessenger
}
When the plugin attaches to another engine, it overwrites that field:
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
binaryMessenger = binding.binaryMessenger
binding.platformViewRegistry.registerViewFactory(
"dotlottie_view",
DotLottieViewFactory(),
)
}
firebase_messaging creates a separate background FlutterEngine and registers plugins there. This replaces the main UI engine's messenger in the static field.
When DotLottieViewFactory subsequently creates a view for the UI engine, it reads the shared messenger and binds the platform-view channels to the background engine instead.
Suggested fix
Avoid storing the engine-specific BinaryMessenger in a static/companion-object field.
Pass the messenger belonging to the attached engine into the view factory and platform view:
class DotLottieViewFactory(
private val binaryMessenger: BinaryMessenger,
) : PlatformViewFactory(StandardMessageCodec.INSTANCE)
Register it per engine:
binding.platformViewRegistry.registerViewFactory(
"dotlottie_view",
DotLottieViewFactory(binding.binaryMessenger),
)
Then pass that messenger to the corresponding DotLottiePlatformView when it is created.
This keeps each platform view bound to the correct Flutter engine and supports multi-engine apps such as those using firebase_messaging.
I did a lot of debugging to find this, with the help of Copilot. So I also let Copilot write up this summary of my hour long debugging journey and the findings. In case anything is unclear, let me know!
Description
Using
dotlottie_flutter: ^0.1.7withfirebase_messagingon Android can cause DotLottie method calls to fail withMissingPluginException.The problem occurs when Firebase Messaging creates its background
FlutterEngine. That engine registersdotlottie_fluttertoo, which overwrites theBinaryMessengerstored by the plugin for the main UI engine.The DotLottie platform view can still be created successfully, but method-channel calls to it are then routed through the background engine's messenger and fail.
Environment
3.44.63.12.2dotlottie_flutter:0.1.7firebase_messaging:16.3.0FlutterFragmentActivitySteps to reproduce
dotlottie_flutter: ^0.1.7.firebase_messaging, including a background message handler.DotLottieViewin the main Flutter engine.Expected behavior
DotLottie method calls from the UI Flutter engine continue working after Firebase Messaging creates and initializes its background engine.
Actual behavior
A call such as
stateMachineSetBooleanInputfails:The native libraries load and the platform view is created, but subsequent calls use the wrong Flutter engine messenger.
Root cause
The Android plugin stores its
BinaryMessengerin a shared static field:When the plugin attaches to another engine, it overwrites that field:
firebase_messagingcreates a separate backgroundFlutterEngineand registers plugins there. This replaces the main UI engine's messenger in the static field.When
DotLottieViewFactorysubsequently creates a view for the UI engine, it reads the shared messenger and binds the platform-view channels to the background engine instead.Suggested fix
Avoid storing the engine-specific
BinaryMessengerin a static/companion-object field.Pass the messenger belonging to the attached engine into the view factory and platform view:
Register it per engine:
binding.platformViewRegistry.registerViewFactory( "dotlottie_view", DotLottieViewFactory(binding.binaryMessenger), )Then pass that messenger to the corresponding
DotLottiePlatformViewwhen it is created.This keeps each platform view bound to the correct Flutter engine and supports multi-engine apps such as those using
firebase_messaging.