From 7ca1e7f3266654bea752b1031d634c6a21f3ac62 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 13:16:50 -0800 Subject: [PATCH 1/2] Destroy the MapView when the map leaves the composition On a configuration change the MapView was only taken off its parent, and a provider switch did nothing at all, so each old MapView stayed alive with the controller it reaches. It is now paused and destroyed through MapViewBase's onDestroyMapView, once. --- .../java/com/mapconductor/here/HereMapView.kt | 52 +++---------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/mapconductor/here/HereMapView.kt b/src/main/java/com/mapconductor/here/HereMapView.kt index e0b6911..4854c23 100644 --- a/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/src/main/java/com/mapconductor/here/HereMapView.kt @@ -1,15 +1,11 @@ package com.mapconductor.here import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.node.Ref import androidx.compose.ui.platform.LocalContext -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.compose.LocalLifecycleOwner import com.here.sdk.mapview.MapProjection as HereMapProjection import com.here.sdk.mapview.MapRenderMode import com.here.sdk.mapview.MapView @@ -44,7 +40,6 @@ import com.mapconductor.here.raster.HereRasterLayerController import com.mapconductor.here.raster.HereRasterLayerOverlayRenderer import java.util.concurrent.atomic.AtomicBoolean import android.annotation.SuppressLint -import android.view.ViewGroup import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine @@ -82,7 +77,6 @@ fun HereMapView( val scope = remember { HereViewScope() } val controllerRef = remember { Ref() } val context = LocalContext.current - val lifecycle = LocalLifecycleOwner.current.lifecycle val registry = remember { scope.buildRegistry() } val cameraState = remember { mutableStateOf(state.cameraPosition) } // Capture the desired initial camera before any early camera callbacks can overwrite state. @@ -224,46 +218,12 @@ fun HereMapView( scope = scope, registry = registry, onMapLoaded = onMapLoaded, - customDisposableEffect = { _, holderRef -> - - // HERE specific DisposableEffect logic - DisposableEffect(lifecycle) { - val stateId = state.id // from BaseMapViewState - val observer = - object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) { - // Do not call here to keep the MapView instance - // holderRef.value?.mapView?.onResume() - } - - override fun onPause(owner: LifecycleOwner) { - // Do not call here to keep the MapView instance - // holderRef.value?.mapView?.onPause() - } - - override fun onDestroy(owner: LifecycleOwner) { - val currentHolder = holderRef.value - if (currentHolder != null) { - val activity = context.findActivity() - if (activity?.isChangingConfigurations == true) { - (currentHolder.mapView.parent as? ViewGroup)?.removeView(currentHolder.mapView) - } else { - // Ensure these calls are safe if mapView might be null or already destroyed - currentHolder.mapView.onPause() - currentHolder.mapView.onDestroy() - // Real teardown: free overlay controllers (tile-server - // routes, marker managers) and cancel the controller scope. - HereMapViewControllerStore.get(stateId)?.destroy() - HereMapViewControllerStore.remove(stateId) // Clean up from your store - } - } - } - } - lifecycle.addObserver(observer) - onDispose { - lifecycle.removeObserver(observer) - } - } + // Created resumed in viewProvider, so paused before it is destroyed. A + // MapView left alive keeps its render loop and, through its listeners, + // the controller with every overlay. + onDestroyMapView = { mapView -> + mapView.onPause() + mapView.onDestroy() }, // Pass content if it needs to be rendered within the overlay providers in MapViewBase, // or handle it here if it's specific to HereMapView structure before calling MapViewBase. From eac94c673b6631cee2fddc25dadd4c836bf75793 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 16:20:26 -0800 Subject: [PATCH 2/2] Read the controller through the state base, which MapViewBase clears The state kept its own controller field, set on attach and never cleared, so a state the app keeps held the destroyed controller and every overlay of the old map. It now reads the base's attached controller, which MapViewBase detaches when the map is torn down. --- src/main/java/com/mapconductor/here/HereViewStateImpl.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mapconductor/here/HereViewStateImpl.kt b/src/main/java/com/mapconductor/here/HereViewStateImpl.kt index 426a0aa..d087c75 100644 --- a/src/main/java/com/mapconductor/here/HereViewStateImpl.kt +++ b/src/main/java/com/mapconductor/here/HereViewStateImpl.kt @@ -28,7 +28,10 @@ class HereViewState( cameraPosition: MapCameraPosition = MapCameraPosition.Default, ) : MapViewState(cameraPosition), HereViewStateInterface { - private var controller: HereMapViewControllerInterface? = null + // Read through the base so that letting go of the controller there (MapViewBase + // does it when the map is torn down) leaves nothing behind here. + private val controller: HereMapViewControllerInterface? + get() = attachedMapController as? HereMapViewControllerInterface private var _mapDesignType: HereMapDesignType = mapDesignType @@ -43,7 +46,6 @@ class HereViewState( override fun getMapViewHolder(): HereViewHolder? = super.getMapViewHolder() as? HereViewHolder internal fun setController(controller: HereMapViewControllerInterface) { - this.controller = controller // controller.setMapDesignType(_mapDesignType) attachController(controller) }