Skip to content

MissingPluginException when firebase_messaging creates a background FlutterEngine #46

Description

@Goddchen

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

  1. Create a Flutter Android app using dotlottie_flutter: ^0.1.7.
  2. Add and configure firebase_messaging, including a background message handler.
  3. Display a DotLottieView in the main Flutter engine.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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