Skip to content

Android: crash with Use after release error #479

@tomquas

Description

@tomquas

Platforms

Android

Version of flutter-maplibre

0.3.3+2

Bug Description

Crash Log

  Caused by com.github.dart_lang.jni.PortProxyBuilder$DartException: Bad
  state: Use after release error
         at
  com.github.dart_lang.jni.PortProxyBuilder._invoke(PortProxyBuilder.java)
         at
  com.github.dart_lang.jni.PortProxyBuilder.invoke(PortProxyBuilder.java:160)
         at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
         at $Proxy8.onCameraMove()
         at org.maplibre.android.maps.CameraChangeDispatcher.executeOnCameraMo
  ve(CameraChangeDispatcher.java:121)
         at org.maplibre.android.maps.CameraChangeDispatcher$CameraChangeHandl
  er.handleMessage(CameraChangeDispatcher.java:172)
         at android.os.Handler.dispatchMessage(Handler.java:114)
         at android.os.Looper.loopOnce(Looper.java:266)
         at android.os.Looper.loop(Looper.java:361)
         at android.app.ActivityThread.main(ActivityThread.java:10320)
         at java.lang.reflect.Method.invoke(Method.java)
         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Runtim
  eInit.java:675)
         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1002)

Claude Analysis

Fix: MapLibre "Use after release" crash on Android

Context

The app crashes on Android when navigating away from a map screen. The
native CameraChangeDispatcher continues to fire onCameraMove() after the
Dart-side JNI proxy objects have been released during widget disposal.

Root cause in maplibre-0.3.3+2 at
lib/src/platform/android/map_state.dart:268-280: the dispose() method
calls _jMap?.release() which releases the Dart JNI reference, but never
unregisters the camera/click listeners from the native MapLibreMap. The
native side still holds references to the proxy objects and tries to
invoke them after release.

The JNI bindings already have removeOnCameraMoveListener(),
removeOnCameraIdleListener(), removeOnCameraMoveStartedListener(),
removeOnMapClickListener(), and removeOnMapLongClickListener() — they just
aren't called.

Approach: Patch maplibre via local copy

Since this is a single-file fix in a pub package, we'll copy the package
source, patch it, and use dependency_overrides.

Step 1: Copy the package

Copy /Users/tom/.pub-cache/hosted/pub.dev/maplibre-0.3.3+2/ into
packages/maplibre/ in the project.

Step 2: Patch packages/maplibre/lib/src/platform/android/map_state.dart

Store listener references — add fields after line 33:

jni.MapLibreMap$OnCameraMoveListener? _onCameraMoveListener;              
jni.MapLibreMap$OnCameraIdleListener? _onCameraIdleListener;              
jni.MapLibreMap$OnCameraMoveStartedListener? _onCameraMoveStartedListener;
jni.MapLibreMap$OnMapClickListener? _onMapClickListener;                  
jni.MapLibreMap$OnMapLongClickListener? _onMapLongClickListener;          

Capture listeners in onMapReady() — assign each .implement() result to its
field before passing to add*Listener(). For example:

_onCameraMoveListener =                                                   
jni.MapLibreMap$OnCameraMoveListener.implement(...);                      
_jMap.addOnCameraMoveListener(_onCameraMoveListener!);                    
     

Same for idle, move-started, click, and long-click listeners.

Remove listeners in dispose() — before _jMap?.release(), add:

if (_onCameraMoveListener != null) {                                      
  _jMap?.removeOnCameraMoveListener(_onCameraMoveListener!);              
  _onCameraMoveListener?.release();                                       
}                                                                         
if (_onCameraIdleListener != null) {                                      
  _jMap?.removeOnCameraIdleListener(_onCameraIdleListener!);              
  _onCameraIdleListener?.release();                                       
}                                                                         
if (_onCameraMoveStartedListener != null) {                               
  _jMap?.removeOnCameraMoveStartedListener(_onCameraMoveStartedListener!);
  _onCameraMoveStartedListener?.release();                                
}                                                                         
if (_onMapClickListener != null) {                                        
  _jMap?.removeOnMapClickListener(_onMapClickListener!);                  
  _onMapClickListener?.release();                                         
}                                                                         
if (_onMapLongClickListener != null) {                                    
  _jMap?.removeOnMapLongClickListener(_onMapLongClickListener!);          
  _onMapLongClickListener?.release();                                     
}                                                                         
        

Step 3: Add dependency override in pubspec.yaml

dependency_overrides:                                                     
  maplibre:                                                               
    path: packages/maplibre                                               
                       

Files to modify

  • pubspec.yaml — add dependency override
  • packages/maplibre/lib/src/platform/android/map_state.dart — the fix
    (store + remove listeners)

Verification

  1. flutter pub get
  2. Build and run on an Android device/emulator
  3. Navigate to map screen, pan/zoom the map
  4. Press back to leave the map screen — should not crash
  5. Repeat rapidly several times
  6. Check adb logcat for any "Use after release" errors

Steps to Reproduce

open a map via Navigator.push, move around, trigger Navigator.pop

Expected Results

no crash

Actual Results

crash log, see above

Code Sample

// Paste your code here

Metadata

Metadata

Assignees

No one assigned

    Labels

    androidAndroid specific issuebugSomething isn't working

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions