From 112b6e0f7916cd37b4e855c6929240b06f30e9ed Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Fri, 6 Nov 2026 19:25:13 -0800 Subject: [PATCH] Keep the map design across configuration changes The saver kept only the id and restored through fromId, so designs outside the presets became Normal and attribution rules were lost. It now saves the layer name and rules. Adds a device round-trip test. --- build.gradle.kts | 5 + .../longdo/LongdoMapViewSaverTest.kt | 97 +++++++++++++++++++ .../longdo/LongdoViewStateImpl.kt | 45 +++++++-- 3 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2e689d6..be28c68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,6 +89,11 @@ dependencies { } else { api("com.mapconductor:compose:$libraryVersion") } + + testImplementation(libs.junit) + // Saver の保存 → 復元を実機の Bundle / Parcel で確かめる(SaverRoundTripTest)。 + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) } // Set project version for NMCP plugin diff --git a/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt b/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt new file mode 100644 index 0000000..eb6b41f --- /dev/null +++ b/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt @@ -0,0 +1,97 @@ +package com.mapconductor.longdo + +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 が [LongdoMapViewSaver] を通って作り直されても、デザイン(id・ + * attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。 + */ +@RunWith(AndroidJUnit4::class) +class LongdoMapViewSaverTest { + 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 keepsDesignOutsideThePresets() { + // 以前は id だけを保存して fromId で戻しており、定義に無い id は Normal になっていた。 + val design = LongdoDesign(id = "my-gray", layerName = "GRAY") + val state = LongdoViewState(mapDesignType = design, id = "longdo-1", initialCameraPosition = camera) + state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false) + + val restored = roundTrip(LongdoMapViewSaver().createSaver(), state) + + assertEquals(design, restored.mapDesignType) + assertCommon(restored, "longdo-1") + } + + @Test + fun keepsAttributionRulesOfAnAppDesign() { + val design = + object : LongdoMapDesignTypeInterface { + override val id: String = "app-design" + override val layerName: String = "POLITICAL" + override val attributionRules: List = rules + + override fun getValue(): String = id + } + val restored = roundTrip(LongdoMapViewSaver().createSaver(), LongdoViewState(design, "longdo-2")) + + assertEquals("app-design", restored.mapDesignType.id) + assertEquals("POLITICAL", restored.mapDesignType.layerName) + assertEquals(rules, restored.mapDesignType.attributionRules) + } + + @Test + fun keepsPresetDesign() { + val restored = roundTrip(LongdoMapViewSaver().createSaver(), LongdoViewState(LongdoDesign.Pastel, "longdo-3")) + assertEquals(LongdoDesign.Pastel, restored.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/longdo/LongdoViewStateImpl.kt b/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt index fcb8400..0e689e7 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt @@ -6,9 +6,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.mapconductor.compose.map.BaseMapViewSaver +import com.mapconductor.core.InternalMapConductorApi +import com.mapconductor.core.map.AttributionRule import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapCameraPositionInterface import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.map.MapViewStateBundle import com.mapconductor.core.map.MapViewStateInterface import android.os.Bundle @@ -62,27 +65,44 @@ class LongdoViewState( /** * [LongdoViewState] の保存・復元を行う Saver。 - * デザインは [LongdoMapDesignTypeInterface.id] を保存し、[LongdoDesign.fromId] で復元する。 + * デザインは id・レイヤ名・attributionRules を保存する。id だけで [LongdoDesign.fromId] に + * 頼ると、定義に無いデザインは [LongdoDesign.Normal] に化け、attributionRules も落ちる。 */ class LongdoMapViewSaver : BaseMapViewSaver() { override fun saveMapDesign( state: LongdoViewState, bundle: Bundle, ) { - bundle.putString(KEY_DESIGN_ID, state.mapDesignType.id) + val design = state.mapDesignType + bundle.putString(KEY_DESIGN_ID, design.id) + bundle.putString(KEY_LAYER_NAME, design.layerName) + MapViewStateBundle.putAttributionRules(bundle, design.attributionRules) } override fun createState( stateId: String, mapDesignBundle: Bundle?, cameraPosition: MapCameraPosition, - ): LongdoViewState { - val designId = mapDesignBundle?.getString(KEY_DESIGN_ID) - return LongdoViewState( - mapDesignType = LongdoDesign.fromId(designId), + ): LongdoViewState = + LongdoViewState( + mapDesignType = restoreDesign(mapDesignBundle), id = stateId, initialCameraPosition = cameraPosition, ) + + private fun restoreDesign(bundle: Bundle?): LongdoMapDesignTypeInterface { + val designId = bundle?.getString(KEY_DESIGN_ID) + // レイヤ名を持たない以前の形式は id から引く。 + val layerName = bundle?.getString(KEY_LAYER_NAME) ?: return LongdoDesign.fromId(designId) + val id = designId ?: LongdoDesign.Normal.id + val rules = MapViewStateBundle.getAttributionRules(bundle) + // LongdoDesign は attributionRules を持てない。規則が無ければ LongdoDesign で戻し、 + // 定義済み(LongdoDesign.Gray など)と等しくなるようにする。 + return if (rules.isEmpty()) { + LongdoDesign(id = id, layerName = layerName) + } else { + RestoredLongdoDesign(id = id, layerName = layerName, attributionRules = rules) + } } override fun getStateId(state: LongdoViewState): String = state.id @@ -92,6 +112,19 @@ class LongdoMapViewSaver : BaseMapViewSaver() { } } +// 公開 API の門番(javap)にコンパニオンの定数が載らないよう、ファイル private にする。 +private const val KEY_LAYER_NAME = "longdo_layer_name" + +/** attributionRules を持つデザインを復元するときの入れ物([LongdoDesign] は規則を持てない)。 */ +@InternalMapConductorApi +internal data class RestoredLongdoDesign( + override val id: String, + override val layerName: String, + override val attributionRules: List, +) : LongdoMapDesignTypeInterface { + override fun getValue(): String = id +} + /** * [LongdoViewState] を生成・記憶する Composable ファクトリ。 *