From afcc1f988098b3022c9d39baf05cdd3a2f504631 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Fri, 6 Nov 2026 19:25:12 -0800 Subject: [PATCH] Keep the map design across configuration changes - The saver kept only the id (Create(id)), dropping elevationSources and attributionRules; it now saves all three. - The controllers reported their default (Streets) to the state when the change listener was attached, so the state's design -- passed in or restored after a rotation -- turned into Streets. The controller is now seeded with the state's design first, and setMapDesignType records it. - Device round-trip test. --- .../arcgis/ArcGISMapViewSaverTest.kt | 84 +++++++++++++++++++ .../com/mapconductor/arcgis/ArcGISMapView.kt | 4 + .../mapconductor/arcgis/ArcGISMapView2D.kt | 4 + .../arcgis/ArcGISMapView2DController.kt | 1 + .../arcgis/ArcGISMapViewController.kt | 1 + .../arcgis/ArcGISMapViewStateImpl.kt | 12 ++- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/androidTest/java/com/mapconductor/arcgis/ArcGISMapViewSaverTest.kt diff --git a/src/androidTest/java/com/mapconductor/arcgis/ArcGISMapViewSaverTest.kt b/src/androidTest/java/com/mapconductor/arcgis/ArcGISMapViewSaverTest.kt new file mode 100644 index 0000000..8869f85 --- /dev/null +++ b/src/androidTest/java/com/mapconductor/arcgis/ArcGISMapViewSaverTest.kt @@ -0,0 +1,84 @@ +package com.mapconductor.arcgis + +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.SaverScope +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.map.AttributionRule +import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapUISettings +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import android.os.Bundle +import android.os.Parcel + +/** + * 回転などの構成変更で state が [ArcGISMapViewSaver] を通って作り直されても、デザイン(id・ + * attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。 + */ +@RunWith(AndroidJUnit4::class) +class ArcGISMapViewSaverTest { + private val rules = + listOf(AttributionRule("© test"), AttributionRule("© ranged", minZoom = 3, maxZoom = 15)) + + private val camera = + MapCameraPosition( + position = GeoPoint(21.38, -157.93), + zoom = 12.0, + bearing = 20.0, + tilt = 10.0, + ) + + @Test + fun keepsElevationSourcesAndAttributionRules() { + val design = + ArcGISDesign.Imagery.copy( + elevationSources = listOf("https://elevation.example.com/ImageServer"), + attributionRules = rules, + ) + val state = ArcGISMapViewState(mapDesignType = design, id = "arcgis-1", cameraPosition = camera) + state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false) + + val restored = roundTrip(ArcGISMapViewSaver().createSaver(), state) + + // 以前は id しか保存しておらず、elevationSources と attributionRules が落ちていた。 + assertEquals(design, restored.mapDesignType) + assertCommon(restored, "arcgis-1") + } + + @Test + fun keepsPresetDesign() { + val state = ArcGISMapViewState(mapDesignType = ArcGISDesign.DarkGray, id = "arcgis-2") + assertEquals(ArcGISDesign.DarkGray, roundTrip(ArcGISMapViewSaver().createSaver(), state).mapDesignType) + } + + private fun assertCommon( + restored: com.mapconductor.core.map.MapViewStateInterface<*>, + id: String, + ) { + assertEquals(id, restored.id) + assertEquals(false, restored.uiSettings.rotateGesture) + assertEquals(false, restored.uiSettings.tiltGesture) + assertEquals(true, restored.uiSettings.scrollGesture) + assertEquals(12.0, restored.cameraPosition.zoom, 1e-9) + assertEquals(20.0, restored.cameraPosition.bearing, 1e-9) + assertEquals(21.38, restored.cameraPosition.position.latitude, 1e-9) + } + + private fun roundTrip( + saver: Saver, + value: T, + ): T { + val saved = with(saver) { SaverScope { true }.save(value) }!! + // 一度 Parcel に書いて読み戻す(プロセスが落ちたあとの復元と同じ経路)。 + val parcel = Parcel.obtain() + try { + parcel.writeBundle(saved) + parcel.setDataPosition(0) + return saver.restore(parcel.readBundle(javaClass.classLoader)!!)!! + } finally { + parcel.recycle() + } + } +} diff --git a/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt b/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt index 3365460..431b49d 100644 --- a/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt +++ b/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt @@ -265,6 +265,10 @@ fun ArcGISMapView( controllerRef.value = mapController mapController.setMapClickListener(onMapClick) mapController.setMapLongClickListener(onMapLongClick) + // 地図は state のデザインで作ってある。コントローラの既定値(Streets)のままだと、 + // 次の行のリスナー登録でそれが state へ返り、アプリが渡した(あるいは + // 回転後に復元した)デザインが Streets に化ける。 + mapController.mapDesignType = state.mapDesignType mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) cameraRestriction?.let { mapController.setCameraRestriction(it) } state.setController(mapController) diff --git a/src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt b/src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt index 7f271f4..96f845e 100644 --- a/src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt +++ b/src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt @@ -204,6 +204,10 @@ fun ArcGISMapView2D( controllerRef.value = mapController mapController.setMapClickListener(onMapClick) mapController.setMapLongClickListener(onMapLongClick) + // 地図は state のデザインで作ってある。コントローラの既定値(Streets)のままだと、 + // 次の行のリスナー登録でそれが state へ返り、アプリが渡した(あるいは + // 回転後に復元した)デザインが Streets に化ける。 + mapController.mapDesignType = state.mapDesignType mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) state.setController(mapController) if (state.mapDesignType.getValue() != diff --git a/src/main/java/com/mapconductor/arcgis/ArcGISMapView2DController.kt b/src/main/java/com/mapconductor/arcgis/ArcGISMapView2DController.kt index 16dd4db..f594118 100644 --- a/src/main/java/com/mapconductor/arcgis/ArcGISMapView2DController.kt +++ b/src/main/java/com/mapconductor/arcgis/ArcGISMapView2DController.kt @@ -309,6 +309,7 @@ class ArcGISMapView2DController( } override fun setMapDesignType(value: ArcGISDesignTypeInterface) { + mapDesignType = value val map = holder.map.map ?: return map.setBasemap(ArcGISDesign.toBasemap(value)) // A map that failed to load -- its basemap was out of reach -- gets diff --git a/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index 5e6ac77..1240a0a 100644 --- a/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -255,6 +255,7 @@ class ArcGISMapViewController( internal var mapDesignTypeChangeListener: ArcGISDesignTypeChangeHandler? = null override fun setMapDesignType(value: ArcGISDesignTypeInterface) { + mapDesignType = value holder.map.scene?.let { scene -> val baseMap = ArcGISDesign.toBasemap(value) defaultCoroutine.launch { diff --git a/src/main/java/com/mapconductor/arcgis/ArcGISMapViewStateImpl.kt b/src/main/java/com/mapconductor/arcgis/ArcGISMapViewStateImpl.kt index a1991be..b0615d0 100644 --- a/src/main/java/com/mapconductor/arcgis/ArcGISMapViewStateImpl.kt +++ b/src/main/java/com/mapconductor/arcgis/ArcGISMapViewStateImpl.kt @@ -10,6 +10,7 @@ import com.mapconductor.core.map.MapCameraPositionInterface import com.mapconductor.core.map.MapPaddings import com.mapconductor.core.map.MapPaddingsInterface import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.map.MapViewStateBundle import com.mapconductor.core.map.MapViewStateInterface import java.util.UUID import android.app.Activity @@ -78,7 +79,10 @@ class ArcGISMapViewSaver : BaseMapViewSaver() { state: ArcGISMapViewState, bundle: Bundle, ) { - bundle.putString("id", state.mapDesignType.id) + val design = state.mapDesignType + bundle.putString("id", design.id) + bundle.putStringArrayList("elevationSources", ArrayList(design.elevationSources)) + MapViewStateBundle.putAttributionRules(bundle, design.attributionRules) } override fun createState( @@ -88,9 +92,13 @@ class ArcGISMapViewSaver : BaseMapViewSaver() { ): ArcGISMapViewState = ArcGISMapViewState( id = stateId, + // ArcGISDesign はデータクラスなので、同じ値で作れば Streets などの定義済みと等しくなる。 + // Create(id) を使うと elevationSources と attributionRules が落ちる。 mapDesignType = - ArcGISDesign.Create( + ArcGISDesign( id = mapDesignBundle?.getString("id") ?: ArcGISDesign.Streets.id, + elevationSources = mapDesignBundle?.getStringArrayList("elevationSources") ?: emptyList(), + attributionRules = MapViewStateBundle.getAttributionRules(mapDesignBundle), ), cameraPosition = cameraPosition, )