MapView is Stuck on Screen Rotation

174
5
2 weeks ago
dev4567
New Contributor III

Context

Prerequisites

1. Have a slow-slow connection: e.g.: on emulator go to Developers options, Network Download Rate Limit, set 128kb.

2. Enable screen rotation.

Actions

  1. Zoom in to initiate tiles loading;
  2. Rotate device when tiles are partially loaded;
  3. Repeat several times.

Result

  • Map composable is not drawn at all for a period of time (no background, nothing);
  • Heap Dump shows memory leak: several ArcGISMap instances are present (one inside the MapView, and others leak to own StateFlow internally.

It seems like basemap's baseLayers remote loading is not cancelled when MapView is destroyed due to lifecycle. cancelLoad on any item (ArcGISMap, Basemap, Layer) doesn't give any effect.

0 Kudos
5 Replies
dev4567
New Contributor III

Code snippet I was playing with to try to understand the issue:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // authentication with an API key or named user is
        // required to access basemaps and other location services
        ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.API_KEY)

        enableEdgeToEdge()

        setContent {
            SampleAppTheme {
                val context = LocalContext.current
                // create a map with a navigation night basemap style
                val map = remember { ArcGISMap(Basemap()) }
                var insets by remember { mutableStateOf(PaddingValues(0.dp)) }

                MapView(
                    modifier = Modifier.fillMaxSize(),
                    arcGISMap = map,
                    isAttributionBarVisible = false,
                    insets = insets
                )
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(WindowInsets.systemBars.asPaddingValues()),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    var count by remember {
                        mutableIntStateOf(0)
                    }
                    Button(
                        onClick = { count++ },
                        content = { Text(text = "Click me") }
                    )
                    Text(text = count.toString())
                }

                LaunchedEffect(Unit) {
                    delay(500L)
                    insets = PaddingValues(32.dp)
                }

                LaunchedEffect(Unit) {
                    delay(100L)
                    map.basemap.value?.baseLayers?.add(ArcGISTiledLayer("https://ibasemaps-api.arcgis.com/arcgis/rest/services/World_Imagery/MapServer"))
                    delay(500L)
                    map.basemap.value?.referenceLayers?.add(ArcGISVectorTiledLayer("https://basemapstyles-api.arcgis.com/arcgis/rest/services/styles/v2/layers/arcgis/imagery/labels"))
                }

                DisposableEffect(Unit) {
                    onDispose {
                        map.basemap
                        println("!")
                    }
                }
            }
        }
    }
}
0 Kudos
dev4567
New Contributor III

Also, what I've found in the other project is that ArcGISMap seems to be leaking inside NetworkAuthenticationInterceptor: 

dev4567_0-1714801211729.png

 

0 Kudos
dev4567
New Contributor III

in previous release 200.3 display-map sample has another issue on rotation with bad connection: I could see two MapView in the heap, which makes me think the problem could be connected with 200.4 leak 🤔  could it be rendering thread / Looper and the lifecycle in the GeoView?

0 Kudos
dev4567
New Contributor III

Also, it feels like reading tiles is done via DefaultDispatcher. As a result -> with bad connection default dispatcher is over-queued easily as it's not intended for long-running operations. If so, http tile reading operations should be switched to IO and asap.

dev4567_1-1714834965800.png

dev4567_0-1714834911882.png

dev4567_2-1714835625103.png

 

0 Kudos
dev4567
New Contributor III

The hacky solution is:

 

private fun ArcGISEnvironment.fixCoroutineScope() = this::class.apply {
    val setCoroutineScopeFun = this.java.methods.find { it.name.startsWith("setScope") } ?: return@apply
    val arcgisCoroutineScope =
        CoroutineScope(
            SupervisorJob() + Dispatchers.IO +
                    CoroutineExceptionHandler { _, throwable ->
                        Log.w("ArcGISEnvironment", throwable)
                    },
        )
    setCoroutineScopeFun.invoke(this.objectInstance, arcgisCoroutineScope)
}

 

Would be cool to see setScope fun being open -> to control the dispatcher, but also e.g. if we want to react on exceptions on our (client) terms.

0 Kudos