From 5fe46093d117138054203df56622246701150162 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 13:16:50 -0800 Subject: [PATCH 1/2] Pause and stop the MapView before destroying it, once The MapView went straight from resumed to onDestroy() when the map left. It is now wound down through onPause / onStop first (unless the Activity already did), and released once whether MapViewBase or the local effect gets there first. --- .../com/mapconductor/tomtom/TomTomMapView.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt b/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt index 29f4a23..1c12098 100644 --- a/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt +++ b/src/main/java/com/mapconductor/tomtom/TomTomMapView.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner import com.mapconductor.compose.map.MapViewBase @@ -38,6 +39,7 @@ import com.mapconductor.tomtom.polyline.TomTomPolylineController import com.mapconductor.tomtom.polyline.TomTomPolylineOverlayRenderer import com.tomtom.sdk.map.display.MapOptions import com.tomtom.sdk.map.display.ui.MapView +import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine @@ -87,6 +89,21 @@ fun TomTomMapView( MapView(context, options).apply { onCreate(null) } } + // 破棄は 1 回だけ。observer が進めた分(onResume / onStart)を巻き戻してから onDestroy。 + // 設定変更では Activity 側で onPause / onStop が済んでいるので onDestroy だけになる。 + val releaseMapView = + remember(mapView) { + val released = AtomicBoolean(false) + + fun() { + if (!released.compareAndSet(false, true)) return + val current = lifecycle.currentState + if (current.isAtLeast(Lifecycle.State.RESUMED)) mapView.onPause() + if (current.isAtLeast(Lifecycle.State.STARTED)) mapView.onStop() + mapView.onDestroy() + } + } + // MapView のライフサイクルをホスト Activity のライフサイクルに同期させる。 // observer 追加時に現在の状態(STARTED/RESUMED)まで catch-up されるため、 // この時点で MapView に onStart/onResume が届く(これがタイル描画に必須)。 @@ -112,7 +129,9 @@ fun TomTomMapView( lifecycle.addObserver(observer) onDispose { lifecycle.removeObserver(observer) - mapView.onDestroy() + // MapViewBase normally released it already (after the controller); + // this covers a map that left before MapViewBase took the view. + releaseMapView() } } @@ -185,6 +204,7 @@ fun TomTomMapView( scope = scope, registry = registry, onMapLoaded = onMapLoaded, + onDestroyMapView = { releaseMapView() }, customDisposableEffect = { _, _ -> // MapView のライフサイクルは上の本体 DisposableEffect で駆動済み。 // ここではコンポジション破棄時にコントローラを破棄してリークを防ぐ。 From ca6beddd178b44a33c06d0518601a35cee93056f 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. --- .../java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt b/src/main/java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt index 6609e88..81b6101 100644 --- a/src/main/java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt +++ b/src/main/java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt @@ -38,10 +38,13 @@ class TomTomMapViewState( this.controller?.setMapDesignType(value) } get() = _mapDesignType - private var controller: TomTomMapViewControllerInterface? = 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: TomTomMapViewControllerInterface? + get() = attachedMapController as? TomTomMapViewControllerInterface internal fun setController(controller: TomTomMapViewControllerInterface) { - this.controller = controller attachController(controller) }