Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.mapconductor.googlemaps

import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.gms.maps.GoogleMap
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.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import android.os.Bundle
import android.os.Parcel

/**
* 回転などの構成変更で state が [GoogleMapViewSaver] を通って作り直されても、デザイン(id・
* attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。
*/
@RunWith(AndroidJUnit4::class)
class GoogleMapViewSaverTest {
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 keepsCustomDesignWithAttributionRules() {
val design = GoogleMapDesign.Custom(GoogleMap.MAP_TYPE_SATELLITE, rules)
val state = GoogleMapViewState(id = "google-1", mapDesignType = design, cameraPosition = camera)
state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false)

val restored = roundTrip(GoogleMapViewSaver().createSaver(), state)

// 以前は id だけを保存して Create(id) で戻しており、Satellite(規則なし)になっていた。
val actual = restored.mapDesignType
assertTrue(actual is GoogleMapDesign.Custom)
assertEquals(GoogleMap.MAP_TYPE_SATELLITE, actual.id)
assertEquals(rules, actual.attributionRules)
assertCommon(restored, "google-1")
}

@Test
fun keepsPresetDesignAsTheSameObject() {
val state = GoogleMapViewState(id = "google-2", mapDesignType = GoogleMapDesign.Hybrid)
assertSame(GoogleMapDesign.Hybrid, roundTrip(GoogleMapViewSaver().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 <T : Any> roundTrip(
saver: Saver<T, Bundle>,
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()
}
}
}
41 changes: 31 additions & 10 deletions src/main/java/com/mapconductor/googlemaps/GoogleMapViewStateImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapPaddings
import com.mapconductor.core.map.MapViewState
import com.mapconductor.core.map.MapViewStateBundle
import com.mapconductor.core.map.MapViewStateInterface
import java.util.UUID
import android.os.Bundle
Expand Down Expand Up @@ -40,13 +41,17 @@ class GoogleMapViewState(

internal fun setController(controller: GoogleMapViewControllerInterface) {
this.controller = controller
// _mapDesignType.let {
// controller.setMapDesignType(it)
// }
// コントローラが覚えているデザインを state のものにしておく。既定の None のままだと、
// 変更リスナーを繋いだ時点で None が state へ返ってきて、復元したデザインを潰す。
// 地図は state の mapType で作られているので、地図の見た目は変わらない。
controller.setMapDesignType(_mapDesignType)
attachController(controller)
}

internal fun onMapDesignTypeChange(value: GoogleMapDesignType) {
// 地図から読み戻した値は mapType(Int)から作った定義済みデザインで、attributionRules を
// 持たない。同じ mapType なら、アプリが渡した(あるいは復元した)デザインを残す。
if (value.getValue() == _mapDesignType.getValue()) return
_mapDesignType = value
}

Expand All @@ -64,26 +69,42 @@ class GoogleMapViewSaver : BaseMapViewSaver<GoogleMapViewState>() {
state: GoogleMapViewState,
bundle: Bundle,
) {
bundle.putInt("id", state.mapDesignType.id)
val design = state.mapDesignType
bundle.putInt("id", design.id)
// 定義済み(Normal など)以外は Custom として戻す。id だけだと Custom の
// attributionRules が落ち、定義に無い id は None になってしまう。
bundle.putBoolean(KEY_CUSTOM, design !== GoogleMapDesign.toMapDesignType(design.id))
MapViewStateBundle.putAttributionRules(bundle, design.attributionRules)
}

override fun createState(
stateId: String,
mapDesignBundle: Bundle?,
cameraPosition: MapCameraPosition,
): GoogleMapViewState =
GoogleMapViewState(
): GoogleMapViewState {
val id = mapDesignBundle?.getInt("id", GoogleMapDesign.Normal.id) ?: GoogleMapDesign.Normal.id
val design =
if (mapDesignBundle?.getBoolean(KEY_CUSTOM) == true) {
GoogleMapDesign.Custom(
id = id,
attributionRules = MapViewStateBundle.getAttributionRules(mapDesignBundle),
)
} else {
GoogleMapDesign.Create(id)
}
return GoogleMapViewState(
id = stateId,
mapDesignType =
GoogleMapDesign.Create(
id = mapDesignBundle?.getInt("id") ?: GoogleMapDesign.Normal.id,
),
mapDesignType = design,
cameraPosition = cameraPosition,
)
}

override fun getStateId(state: GoogleMapViewState): String = state.id
}

// 公開 API の門番(javap)にコンパニオンの定数が載らないよう、ファイル private にする。
private const val KEY_CUSTOM = "custom"

@Composable
fun rememberGoogleMapViewState(
mapDesign: GoogleMapDesign = GoogleMapDesign.Normal,
Expand Down
Loading