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
430 stars 126 forks source link

Compose Navigation between Maps with say One point annotation fails #2331

Closed MichelVHoward closed 1 month ago

MichelVHoward commented 1 month ago

Environment

Observed behavior and steps to reproduce

When using compose navigation directly from one MapboxMap composable, with One pointAnnotation, to the antother MapboxMap composable, the annotation on the navigated to composable disappears after a short while. It does not happen if you have a composable navigation step in between, and it does not happen in simple conditional rendering controlled by some simple boolean state. The code for trying the three different scenarios are shown in the following code and preview of a composable, which i easy to load into a project connected to MapBox. Of course you have to change theme and R.drawable.Ids.

The behavior is the same if coding the contents in a normal Activity!

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.mapbox.geojson.Point
import com.mapbox.maps.MapboxExperimental
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.MapViewportState
import com.mapbox.maps.extension.compose.annotation.generated.PointAnnotation
import com.odysseytrove.R

@OptIn(MapboxExperimental::class)
@Composable
fun TestMap(iconResource: Int, onclick: () -> Unit) {
    MapboxMap(
        modifier = Modifier.fillMaxWidth(),
        mapViewportState = MapViewportState().apply {
            setCameraOptions {
                center(Point.fromLngLat(10.4963836, 57.6877367))
                zoom(2.0)
                build()
            }
        },
    ) {
        PointAnnotation(
            point = Point.fromLngLat(10.4963876, 57.6877367),
            iconImageBitmap = bitmapFromResource(resId = iconResource),
            iconSize = 0.1,
            iconOpacity = 1.0,
            onClick = {
                onclick()
                true
            }
        )
    }
}

@Preview
@Composable
fun PreviewWithDisappearingLocation() {
    val controller = rememberNavController()
    NavHost(navController = controller, startDestination = "test1") {
        composable("test1") {
            TestMap(R.drawable.home_553416) {
                controller.navigate(
                    "test2",
                    NavOptions.Builder().setPopUpTo("test1", inclusive = true).build()
                )
            }
        }
        composable("test2") {
            TestMap(R.drawable.location) {
                controller.navigate("test1")
            }
        }
    }
}

@Preview
@Composable
fun PreviewWithIntermediateStepWorksLastStepNot() {
    val controller = rememberNavController()
    NavHost(navController = controller, startDestination = "test1") {
        composable("test1") {
            TestMap(R.drawable.home_553416) {
                controller.navigate("test3")
            }
        }
        composable("test2") {
            TestMap(R.drawable.location) {
                controller.navigate("test1")
            }
        }
        composable("test3") {
            LaunchedEffect(Unit) {
                controller.navigate(
                    "test2",
                    NavOptions.Builder().setPopUpTo("Test1", inclusive = false).build()
                )
            }
        }
    }
}

@Preview
@Composable
fun PreviewSimpleConditionalRenderingWorks() {
    var showFirst by remember {
        mutableStateOf(true)
    }
    if (showFirst) {
        TestMap(R.drawable.home_553416) {
            showFirst = false
        }
    } else {
        TestMap(R.drawable.location) {
            showFirst = true
        }
    }
}

Expected behavior

The expected behavior is that navigating to a MapboxMap to another keeps the pointannotation in the navigated to MapboxMap The two last preview tests do indeed do this, but not the first

Notes / preliminary analysis

I have tried many ways to avoid this problem but the best hack i have come up with is a fake destination in between the navigation destinations as shown in the second preview

Additional links and references

I have attached a video and here is a logcat snippet that looks suspicious

https://github.com/mapbox/mapbox-maps-android/assets/93967568/d4bb48bb-1d27-494e-b2ec-c375678f2779

2024-04-05 13:26:45.854 14254-14254 Mapbox                  com.odysseytrove                     E  [maps-core]: Attempted to stop performance statistics collection before calling render()
2024-04-05 13:26:45.863 14254-14254 ViewRootIm...wActivity] com.odysseytrove                     I  registerCallbackForPendingTransactions
2024-04-05 13:26:45.867 14254-14298 SurfaceView             com.odysseytrove                     D  113072419 windowPositionLost, frameNr = 79
2024-04-05 13:26:45.868 14254-14302 ViewRootIm...wActivity] com.odysseytrove                     I  mWNT: t=0xb400007264c1b1f0 mBlastBufferQueue=0xb400007374c23ed0 fn= 79 mRenderHdrSdrRatio=1.0 caller= android.view.ViewRootImpl$6.onFrameDraw:5639 android.view.ViewRootImpl$2.onFrameDraw:2144 android.view.ThreadedRenderer$1.onFrameDraw:792 
2024-04-05 13:26:45.868 14254-14254 Mapbox                  com.odysseytrove                     I  [maps-android\Mbgl-RenderThread]: renderThreadPrepared=false and Android surface is not valid (isValid=false). Waiting for new one.
2024-04-05 13:26:45.872 14254-14254 Mapbox                  com.odysseytrove                     E  [maps-android\AnnotationManagerImpl]: Can't delete annotation: com.mapbox.maps.plugin.annotation.generated.PointAnnotation@3d9ce20, the annotation isn't an active annotation.
2024-04-05 13:26:45.884 14254-14254 Mapbox                  com.odysseytrove                     W  [maps-android\MapViewportState]: tryInvokingOperation invoked when Map is not set, added the operation as pending operation.
2024-04-05 13:26:45.897 14254-14254 Mapbox                  com.odysseytrove                     E  [maps-android\AnnotationManagerImpl]: Can't update annotation: com.mapbox.maps.plugin.annotation.generated.PointAnnotation@85f4138, the annotation isn't an active annotation.
pengdev commented 1 month ago

@MichelVHoward there was a known issue about sharing the root map node between map instances, which is fixed in v11.3.0-rc.1, could you try the latest Maps SDK https://github.com/mapbox/mapbox-maps-android/releases/tag/v11.3.0-rc.1 ?

MichelVHoward commented 1 month ago

I have tested with the mentioned version at it seems to work now thanx :-)

MichelVHoward commented 1 month ago

It works