Hey has anyone else seen this issue ?
I think its a timing issue on ESRI's API side between the navigation changed event and the dispose being called. My understanding is dispose() is probably freeing the memory in the native layer and nulling the reference in the jni layer.
My guess is even though I call removeNavigationChangedListener() first and then dispose(). The removeNavigationChangedListener might not be processing until after dispose() so when an event comes in due to timing this results in a NPE crash in jni since the coreGeoView is null by then.
Does this look like a possible hypothesis ?
Anyone what can be causing this crash, how to reproduce, or how to prevent it ?
We were only able to reproduce it once.
ESRI Android SDK version: 100.15.1
06-05 10:35:59.163 E/AndroidRuntime( 4650): FATAL EXCEPTION: main
06-05 10:35:59.163 E/AndroidRuntime( 4650): com.esri.arcgisruntime.ArcGISRuntimeException: Null pointer.: object cannot be null.
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.esri.arcgisruntime.internal.jni.CoreGeoView.nativeGetCurrentViewpoint(Native Method)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.esri.arcgisruntime.internal.jni.CoreGeoView.a(SourceFile:6)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.esri.arcgisruntime.internal.mapping.view.g.a(SourceFile:11)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.esri.arcgisruntime.mapping.view.GeoView.getCurrentViewpoint(SourceFile:1)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at navigationChangedEvent$lambda$3(MapViewManager.kt:72)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.*.map.views.MapViewManager.$r8$lambda$rVRzbl0bIqRbN635fKa6xg5_5oI(Unknown Source:0)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.*.map.views.MapViewManager$$ExternalSyntheticLambda3.navigationChanged(Unknown Source:2)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.esri.arcgisruntime.internal.mapping.view.t$a.run(SourceFile:1)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at android.os.Handler.handleCallback(Handler.java:938)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at android.os.Handler.dispatchMessage(Handler.java:99)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at android.os.Looper.loop(Looper.java:223)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at android.app.ActivityThread.main(ActivityThread.java:7725)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at java.lang.reflect.Method.invoke(Native Method)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
06-05 10:35:59.163 E/AndroidRuntime( 4650): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
06-05 10:35:59.305 I/am_crash( 1375): [4650,0,*,551042631,com.esri.arcgisruntime.ArcGISRuntimeException,Null pointer.,SourceFile,-2]
Solved! Go to Solution.
Hi Dhruvin,
Calling removeNavigationChangedListener should remove this listener immediately.
But there seems to be a timing issue judging from your call stack, as the listener appears to be invoked by android.os.Looper.loop. The way listeners are invoked works as follows - If a listener was added on the main thread, it will always be invoked on the main thread. This means that the listener may have to be invoked by posting the event onto the main thread from some other thread, which I think is the case here. When the event is posted onto the main thread, I suppose there is a small time window inbetween which you may have removed the listener and called dispose on the GeoView, but the navigation changed event is already on the main thread's event queue and will be executed regardless.
I wonder if you could avoid this issue by removing the listener in onPause instead of onDestroyView. Alternatively, you could keep track of the "disposed" state of your fragment with a flag. Then check this flag in your MapViewManager before processing any event.
@RamaChintapalli can you help take a look at this ?
Hi Dhruvin,
When do you call the dispose in your app thats conflicting with your NavigationChanged event as you suggest. Typically it should be called when you're done with the view to release resources, so technically should not conflict with the events on the view unless being explicitly used else where while view is still in action.
Thanks
Rama
This was found during a day night mode change which causes the fragment view to be destroyed and recreated.
We have a cleanup() function called in onDestroyView of our fragment that holds the MapView.
The cleanup() calls:
removeNavigationChangedListener(navigationChangedEvent)
...
dispose()
I am not sure why the navigationChanged listener triggered a callback after called dispose() but the backtrace suggests that right ?
When exactly does navigationChanged get triggered ?
Is there a possibility the listener doesn't get cleaned up properly ?
Is there a possibility that remove is called before dispose but remove is processed after dispose, thus causing the crash ?
Hi Dhruvin,
Calling removeNavigationChangedListener should remove this listener immediately.
But there seems to be a timing issue judging from your call stack, as the listener appears to be invoked by android.os.Looper.loop. The way listeners are invoked works as follows - If a listener was added on the main thread, it will always be invoked on the main thread. This means that the listener may have to be invoked by posting the event onto the main thread from some other thread, which I think is the case here. When the event is posted onto the main thread, I suppose there is a small time window inbetween which you may have removed the listener and called dispose on the GeoView, but the navigation changed event is already on the main thread's event queue and will be executed regardless.
I wonder if you could avoid this issue by removing the listener in onPause instead of onDestroyView. Alternatively, you could keep track of the "disposed" state of your fragment with a flag. Then check this flag in your MapViewManager before processing any event.
Thanks @GuntherHeppner, this was a very helpful explanation and workaround.
I think we will go with isDisposed flag workaround.
Does this affect all listeners interacting with the MapView ?
If not, can you tell me which ones are affected from the list below:
We use the following listeners:
removeViewpointChangedListener
removeNavigationChangedListener
removeDrawStatusChangedListener
removeAttributionTextChangedListener
removeAutoPanModeChangedListener
removeDataSourceStatusChangedListener
removeLocationChangedListener
Is there a way this issue can be addressed thru Android ESRI SDK itself ?
Can we at least update API documentation on this ?
I don't see it stated in the API documentation or coded this way in the ESRI samples either so we didn't think this would be a concern.
Thanks,
Dhruvin
@Dhruvin - yes, this could affect all MapView listeners, since they all follow the same pattern, i.e. attempting to be invoked on the main thread if possible.
I agree that this is a bug in the SDK, and we should look for a way to either suppress these events once the MapView is disposed or at least mitigate it by exposing a `GeoView.isDisposed` property, so clients can just check this flag. I have created an issue and we will look into it for one of our upcoming patch releases.
Thanks alot Gunther, let me know the bug number if possible.
Hi Dhruvin,
Please use the following bug id to track it - BUG-000159265.
Thanks
Rama