Skip to content

fix(android): guard onDetachedFromEngine against null channel (NPE on headless engine teardown)#26

Open
Ant1Miller wants to merge 1 commit into
mainfrom
fix/android-ondetachedfromengine-npe
Open

fix(android): guard onDetachedFromEngine against null channel (NPE on headless engine teardown)#26
Ant1Miller wants to merge 1 commit into
mainfrom
fix/android-ondetachedfromengine-npe

Conversation

@Ant1Miller

Copy link
Copy Markdown

Problem

Reported in ZD #6122 (SuperLyfe / MAHHP), reproducible on Android and confirmed against source.

MethodChannel channel is instantiated in onAttachedToActivity (line 364), not in onAttachedToEngine (which only stores the BinaryMessenger). onDetachedFromEngine then called channel.setMethodCallHandler(null) unconditionally.

A Flutter engine that registers the plugin but never attaches an Activity — the everyday case being an FCM background isolate, but also WorkManager / background-locator engines — reaches onDetachedFromEngine with channel == null and crashes:

java.lang.NullPointerException: Attempt to invoke virtual method
'void io.flutter.plugin.common.MethodChannel.setMethodCallHandler(...)' on a null object reference
    at co.tryterra.terra_flutter_bridge.TerraFlutterPlugin.onDetachedFromEngine(TerraFlutterPlugin.java:482)

Present on 0.9.4 (this branch) and reported identical on 0.8.3.

Fix

Null-guard the teardown and clear the field:

if (channel != null) {
  channel.setMethodCallHandler(null);
  channel = null;
}
  • Activity-attached teardown is unchanged (channel is non-null, handler still detaches).
  • Headless teardown skips the deref instead of crashing.
  • Nulling the field makes a double-detach idempotent and lets the old channel GC.

Verification

No Android unit-test harness exists in this package (src/test is empty), so this is verified by inspection rather than a runtime test: line 482 is the exact frame in the customer's crash trace, and the guard makes that dereference safe on the null-channel path while leaving the Activity-attached path untouched. Adversarial review confirmed no use-after-null on any other path (channel is read only here, written only in onAttachedToActivity).

Scope / risk

Additive defensive guard on a path that previously always crashed for headless engines — no behaviour change for Activity-attached apps. Risk to other customers: none.

Deliberately out of scope (the customer's secondary observations, to be handled separately): the success: true masking in init callbacks, the empty onDetachedFromActivity (handler leak on activity re-attach), and the getAthlete notImplemented branch.

🤖 Generated with Claude Code

The MethodChannel is created in onAttachedToActivity, so a Flutter engine
that registers the plugin without ever attaching an Activity (e.g. an FCM
background isolate) reached onDetachedFromEngine with channel still null and
crashed with a NullPointerException on teardown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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