mapbox / mapbox-maps-android

Interactive, thoroughly customizable maps in native Android powered by vector tiles and OpenGL.
https://www.mapbox.com/mobile-maps-sdk
Other
476 stars 133 forks source link

java.lang.IllegalStateException: Mutating MapInitOptions during composition is not allowed. #2183

Closed anonym24 closed 6 months ago

anonym24 commented 1 year ago

Environment

I'm trying to use MapBox in Compose and set the needed location on the map using mapViewportState.flyTo() function but it fails with the mentioned exception for some reason, and it's related to using mapInitOptionsFactory = { MapInitOptions(...) }, if I remove mapInitOptionsFactory line code then it works, but it means of course I get default map style

val mapViewportState = rememberMapViewportState
LaunchedEffect(key1 = userInitialLocation) {
    userInitialLocation?.let { userLocation ->
        mapViewportState.flyTo(
            cameraOptions = mapViewportState.cameraState.toCamera
                .center(
                    Point.fromLngLat(
                        userLocation.longitude,
                       userLocation.latitude
                    )
                )
                .zoom(16.0)
                .build()
        )
    }
}
val context = LocalContext.current
if (isLocationPermissionGranted) {
    MapboxMap(
        modifier = Modifier.fillMaxSize(),
        mapViewportState = mapViewportState,
         mapInitOptionsFactory = { // if I comment out this part then it works fine
             MapInitOptions(
                 context = context,
                 styleUri = Style.DARK,
             )
         }
    )

Error was captured in composition while live edit was enabled.
                 java.lang.IllegalStateException: Mutating MapInitOptions during composition is not allowed.
                    at com.mapbox.maps.extension.compose.internal.MapboxMapNodeKt$MapboxMapComposeNode$2$1.invoke(MapboxMapNode.kt:136)
                    at com.mapbox.maps.extension.compose.internal.MapboxMapNodeKt$MapboxMapComposeNode$2$1.invoke(MapboxMapNode.kt:135)
                    at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1712)
                    at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1710)
                    at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:818)
                    at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:849)
                    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:625)
                    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:537)
                    at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:41)
                    at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
                    at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
                    at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
                    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229)
                    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
                    at android.view.Choreographer.doCallbacks(Choreographer.java:899)
                    at android.view.Choreographer.doFrame(Choreographer.java:827)
                    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
                    at android.os.Handler.handleCallback(Handler.java:942)
                    at android.os.Handler.dispatchMessage(Handler.java:99)
                    at android.os.Looper.loopOnce(Looper.java:201)
                    at android.os.Looper.loop(Looper.java:288)
                    at android.app.ActivityThread.main(ActivityThread.java:7872)
                    at java.lang.reflect.Method.invoke(Native Method)
                    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
pengdev commented 1 year ago

Hey @anonym24 thanks for trying out the compose extension. At the moment, the MapInitOptions is not mutable and can not be used to alter the style during composition.

Until we expose proper runtime styling APIs for compose extension, you can get the raw map controller using MapEffect and load the customised style with it.

pengdev commented 1 year ago

@anonym24 Took a second look at the report, seems the crash happened when live edit is enabled:

Error was captured in composition while live edit was enabled.

Could you confirm the crash does not happen without live edit enabled?

bamsbamx commented 1 year ago

@anonym24 Took a second look at the report, seems the crash happened when live edit is enabled:

Error was captured in composition while live edit was enabled.

Could you confirm the crash does not happen without live edit enabled?

I can confirm this happens even without live edit. Below is the stack trace. I wanted to change the Style URI on runtime (based on user selection), but using MapEffect seems like didn't change anything, so I tried to do it in MapInitOptions and the crash happened

23:46:55.970 ACRA                       E  ACRA caught a IllegalStateException for com.anrapps.zenit.tracks.debug
                                       java.lang.IllegalStateException: Mutating MapInitOptions during composition is not allowed.
                                        at com.mapbox.maps.extension.compose.internal.MapboxMapNodeKt$MapboxMapComposeNode$2$1.invoke(MapboxMapNode.kt:136)
                                        at com.mapbox.maps.extension.compose.internal.MapboxMapNodeKt$MapboxMapComposeNode$2$1.invoke(MapboxMapNode.kt:135)
                                        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1712)
                                        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1710)
                                        at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:818)
                                        at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:849)
                                        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:625)
                                        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:537)
                                        at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:41)
                                        at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
                                        at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
                                        at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
                                        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1299)
                                        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309)
                                        at android.view.Choreographer.doCallbacks(Choreographer.java:923)
                                        at android.view.Choreographer.doFrame(Choreographer.java:847)
                                        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283)
                                        at android.os.Handler.handleCallback(Handler.java:942)
                                        at android.os.Handler.dispatchMessage(Handler.java:99)
                                        at android.os.Looper.loopOnce(Looper.java:226)
                                        at android.os.Looper.loop(Looper.java:313)
                                        at android.app.ActivityThread.main(ActivityThread.java:8762)
                                        at java.lang.reflect.Method.invoke(Native Method)
                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

The code

    @OptIn(MapboxExperimental::class)
    @Composable
    private fun Map(mapStyleUri: String) {
        MapboxMap(
            modifier = Modifier.fillMaxSize(),
            mapInitOptionsFactory = { context ->
                MapInitOptions(
                    context = context,
                    styleUri = mapStyleUri,
                    cameraOptions = CameraOptions.Builder()
                        .center(Point.fromLngLat(24.9384, 60.1699))
                        .zoom(9.0)
                        .build()
                )
            }
        ) {
            // CHANGING URI HERE DOESN'T HAVE ANY EFFECT
    //        MapEffect(Unit) { mapView ->
    //            mapView.mapboxMap.loadStyle(
    //                style(mapStyleUri) {
    //                    +projection(ProjectionName.GLOBE)
    //                    +rasterDemSource("raster-dem") {
    //                        url("mapbox://mapbox.terrain-rgb")
    //                    }
    //                    +terrain("raster-dem") {
    //                        exaggeration(1.25)
    //                    }
    //                }
    //            )
    //        }
        }
    }
joaovalentee commented 11 months ago

I can confirm this happens even without live edit. Below is the stack trace. I wanted to change the Style URI on runtime (based on user selection), but using MapEffect seems like didn't change anything, so I tried to do it in MapInitOptions and the crash happened

Hey @bamsbamx. MapEffect worked for me but instead of using Unit as the MapEffect key input, I used the variable that holds the styleUri:

val styleUri = if (isSystemInDarkTheme()) Style.DARK else Style.LIGHT

MapboxMap(
    modifier = Modifier.fillMaxSize(),
    mapInitOptionsFactory = { context ->
        MapInitOptions(
            context = context,
            styleUri = Style.DARK,
            cameraOptions = CameraOptions.Builder()
                .center(Point.fromLngLat(24.9384, 60.1699))
                .zoom(9.0)
                .build()
        )
    }
) {
    MapEffect(styleUri) { mapView ->
        mapView.mapboxMap.loadStyle(styleUri)
    }
}
pengdev commented 7 months ago

Hi @joaovalentee @bamsbamx @anonym24 we have recently introduced style composable functions in the latest 11.3.0-beta.1 release, please feel free to give it a try and see it fulfils your needs. Please note we are still iterating on this API so expect more changes to this API.

pengdev commented 6 months ago

Closing as related APIs have been refined, please check the latest compose extension release and test app for reference.