Dependencies :
implementation(platform("com.esri:arcgis-maps-kotlin-toolkit-bom:200.5.0"))
implementation("com.esri:arcgis-maps-kotlin-toolkit-geoview-compose")
I am using Compose MapView to display list of map using lazy column
App is crashing when user clicks on MapView just when it is InProgress draw state.
Below is my code :
MapView(
modifier = modifier.fillMaxSize(),
arcGISMap = map,
mapViewProxy = mapViewProxy,
graphicsOverlays = graphicsOverlays,
mapViewInteractionOptions = MapViewInteractionOptions(
isEnabled = false,
isMagnifierEnabled = false,
isZoomEnabled = false,
isRotateEnabled = false,
isFlingEnabled = false,
allowMagnifierToPan = false,
isPanEnabled = false
),
isAttributionBarVisible = false,
insets = insertPaddingValues,
viewpointPersistence = ViewpointPersistence.None,
onSingleTapConfirmed = { onTap() },
onDrawStatusChanged = {
if (it == DrawStatus.Completed) {
coroutineScope.launch {
mapViewProxy.exportImage().onSuccess { image ->
// export map image
}
}
}
}
)
Below is the error log :
10:13:42.211 E FATAL EXCEPTION: main
Process: za.co.tracker.consumer, PID: 29653
java.lang.NullPointerException: A null pointer.
at com.arcgismaps.internal.ErrorFactoryKt.toPlatformExceptionOrNull(ErrorFactory.kt:202)
at com.arcgismaps.internal.ErrorFactoryKt.convertToPublic(ErrorFactory.kt:111)
at com.arcgismaps.internal.ErrorFactory.createThrowableFromCoreError(ErrorFactory.kt:59)
at com.arcgismaps.internal.jni.CoreGeoView.nativeGeometryEditorInteractionTap(Native Method)
at com.arcgismaps.internal.jni.CoreGeoView.geometryEditorInteractionTap(CoreGeoView.java:403)
at com.arcgismaps.mapping.view.GeoView.geometryEditorTap$arcgis_maps_kotlin_release(GeoView.kt:1250)
at com.arcgismaps.mapping.view.internal.DefaultMapViewOnTouchListener.onRecognizerSingleTapConfirmed(DefaultMapViewOnTouchListener.kt:317)
at com.arcgismaps.mapping.view.internal.DefaultMapViewOnTouchListener.access$onRecognizerSingleTapConfirmed(DefaultMapViewOnTouchListener.kt:83)
at com.arcgismaps.mapping.view.internal.DefaultMapViewOnTouchListener$onLifecycleReady$$inlined$launchAndCollectInMapViewResumed$2$1$1.emit(FlowExtensions.kt:51)
at kotlinx.coroutines.flow.SharedFlowImpl.collect$suspendImpl(SharedFlow.kt:392)
at kotlinx.coroutines.flow.SharedFlowImpl$collect$1.invokeSuspend(Unknown Source:19)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:231)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:187)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:159)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:504)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:493)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:364)
at kotlinx.coroutines.flow.SharedFlowImpl.tryEmit(SharedFlow.kt:409)
at com.arcgismaps.mapping.view.internal.SingleTapConfirmedGestureRecognizer.emitSingleTapConfirmed(SingleTapConfirmedGestureRecognizer.kt:169)
at com.arcgismaps.mapping.view.internal.SingleTapConfirmedGestureRecognizer.access$emitSingleTapConfirmed(SingleTapConfirmedGestureRecognizer.kt:47)
at com.arcgismaps.mapping.view.internal.SingleTapConfirmedGestureRecognizer$onTouch$2.invokeSuspend(SingleTapConfirmedGestureRecognizer.kt:141)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:231)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:164)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:504)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:493)
at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:591)
at kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run(Runnable.kt:15)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:240)
at android.os.Looper.loop(Looper.java:351)
at android.app.ActivityThread.main(ActivityThread.java:8423)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@5210a97, Dispatchers.Main.immediate]
Caused by: com.arcgismaps.exceptions.ArcGISExceptionImpl: message=A null pointer., additionalMessage=object cannot be null., errorCode=1
10:13:42.211 E at com.arcgismaps.exceptions.ArcGISException$Companion.create$arcgis_maps_kotlin_release(ArcGISException.kt:86)
at com.arcgismaps.internal.ErrorFactoryKt.convertToPublic(ErrorFactory.kt:128)
... 37 more
Solved! Go to Solution.
Thanks for sending us your code - we are now able to reproduce the crash with some consistency. We have identified the issue and implemented a fix, which will be included in version 200.6.0 of the SDK.
@PrashantGaykar - we are investigating this issue but we need a reproducer. Would you be able to share your code that hosts the MapView in a lazy column. Also if you have more details on reproducer steps that would be helpful, i.e. does it happen every time the user taps on the MapView or only under certain circumstances? For example you mention it happens when drawing is in progress. Does a user scroll through multiple MapViews within a lazy column and while doing that, they immediately tap on the MapView that comes into view?
Hi @GuntherHeppner , thanks for your response.
I cannot expose the exact code but let me give you an idea how its UI looks on screen
As shown in above design we are loading user's trip with MapView and details inside the compose card.
Code as below
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(
items = trips,
key = { trip -> trip.id }
) { item ->
TripItemCard(
tripData = item
)
}
}
Inside this TripItemCard I have added the MapView.
Now problem with using MapView in LazyColumn was whenever we scroll up/down the MapView was reinitiating due to re-composition and It led to crash if we keep doing that (scroll up/down) for a while.
To solve this issue and for optimization we exported the map image. Once the image is ready we cached it and replaced it with MapView.
Steps to reproduce the exception:
1) Load trip list screen with MapView. First visible MapView's on screen will get loaded immediatley.
2) Scroll down. Now MapView on new scrolled items will start loading with grayish background. This is the moment we must tap quickly on any MapView when it is grayish and it will led to crash with exception I posted.
So you are right about "Does a user scroll through multiple MapViews within a lazy column and while doing that, they immediately tap on the MapView that comes into view?"
One more thing, I am getting this exception even if I set onSingleTapConfirmed = null in MapView.
At least there should be way to disable the tap on MapView.
Let me know if you are able to reproduce it.
@PrashantGaykar - We've had a go at reproducing this issue. As with your example we have a LazyColumn containing cards, each containing a MapView and some text. I can reproduce the crash that occurs when repeatedly scrolling up and down the column quickly, but not the crash caused by tapping on a MapView while it's in the inProgress draw state.
Without a reproducer we're somewhat guessing as to where the issue is - the call stack suggests an issue with GeometryEditor, but you're not passing a GeometryEditor to the MapView in the code provided. Do you set a GeometryEditor elsewhere? I'm also interested in the contents of the graphicsOverlays you pass in to the MapView. Are the graphics in here made with GeometryEditor?
Hi @Oliver_Smith,
I am not using GeometryEditor in MapView.
Graphics added in GraphicsOverlay are only start and end icons.
but I do export the map image and display it. I guess that is unusal.
I am posting full code of MapItem from TripItemCard for your reference. May be this could help.
@Composable
fun MapItemCached(
modifier: Modifier = Modifier,
startPoint: Point,
endPoint: Point,
startPictureMarkerSymbol: PictureMarkerSymbol,
endPictureMarkerSymbol: PictureMarkerSymbol,
tripId: Long = 0,
cachedMapImage: Bitmap? = null,
onTap: () -> Unit,
event: (Event) -> Unit,
) {
val map by remember {
mutableStateOf(ArcGISMap(BasemapStyle.ArcGISImagery))
}
val graphicsOverlay = remember { GraphicsOverlay() }
val graphicStart = Graphic(startPoint, startPictureMarkerSymbol)
val graphicEnd = Graphic(endPoint, endPictureMarkerSymbol)
graphicsOverlay.graphics.add(graphicStart)
graphicsOverlay.graphics.add(graphicEnd)
// Create a list of graphics overlays used by the MapView
val graphicsOverlays = remember { listOf(graphicsOverlay) }
val coroutineScope = rememberCoroutineScope()
val mapViewProxy = remember {
MapViewProxy()
}
// display map image instead of map if it is available
if (cachedMapImage == null) {
MapView(
modifier = modifier.fillMaxSize(),
arcGISMap = map,
mapViewProxy = mapViewProxy,
graphicsOverlays = graphicsOverlays,
mapViewInteractionOptions = MapViewInteractionOptions(
isEnabled = false,
isMagnifierEnabled = false,
isZoomEnabled = false,
isRotateEnabled = false,
isFlingEnabled = false,
allowMagnifierToPan = false,
isPanEnabled = false
),
isAttributionBarVisible = false,
viewpointPersistence = ViewpointPersistence.None,
onSingleTapConfirmed = { onTap() },
onDrawStatusChanged = {
if (it == DrawStatus.Completed) {
coroutineScope.launch {
mapViewProxy.exportImage().onSuccess { image ->
// save this image in viewModel and retrieve back as cachedMapImage.
event(Event.CacheMapImage(tripId, image.bitmap))
}
}
}
}
)
} else {
Image(
bitmap = cachedMapImage.asImageBitmap(),
contentDescription = "Map Image"
)
}
}
Hi @PrashantGaykar - thanks for providing that extra code and for confirming you don't use GeometryEditor. I've implemented your code with an alteration to the event parameter - judging by your code comment mine does something functionally similar, but the implementation is quite different. Is the Event class you use something you've written?
Unfortunately I'm still not able to reproduce the issue. I've made my repro available on a branch of the public samples repo, as a sample called "Geoview NPE Repro". Can you run this on your device and report if you are still seeing crashes when interacting with an unloaded MapView? If not, can you modify the reproducer closer to your code until you do?
I have written the Event class. It is a Seal class used to pass event and data to view model.
I will go through your repo and will try to reproduce the bug.
Sorry for the late reply.
I have modifiied the code from your branch to produce the NPE and have raised the PR Request for the same.
In MainScreen.kt I have added following method and it is causing NPE when called from MapItemCached() composable and tapped of map.
private fun setupBaseMap(
map: ArcGISMap,
envelope: Envelope,
) {
try {
map.initialViewpoint = Viewpoint(envelope)
map.maxScale = 1500.00
map.minScale = 3.7E7
} catch (e: ArcGISException) {
e.printStackTrace()
}
}
which I forgot to add this method in my initial code.
Please review the PR and let me know.
Thanks for sending us your code - we are now able to reproduce the crash with some consistency. We have identified the issue and implemented a fix, which will be included in version 200.6.0 of the SDK.