Skip to content

Commit 1821350

Browse files
Duy PhạmDuy Phạm
authored andcommitted
Implement rememberSaveable for W3WMapManager
1 parent 5e63b68 commit 1821350

File tree

2 files changed

+112
-11
lines changed

2 files changed

+112
-11
lines changed

lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.runtime.getValue
1212
import androidx.compose.runtime.mutableStateOf
1313
import androidx.compose.runtime.remember
1414
import androidx.compose.runtime.rememberCoroutineScope
15+
import androidx.compose.runtime.saveable.rememberSaveable
1516
import androidx.compose.runtime.setValue
1617
import androidx.compose.ui.Alignment
1718
import androidx.compose.ui.Modifier
@@ -21,8 +22,8 @@ import androidx.compose.ui.layout.onGloballyPositioned
2122
import com.google.accompanist.permissions.ExperimentalPermissionsApi
2223
import com.google.accompanist.permissions.rememberMultiplePermissionsState
2324
import com.what3words.components.compose.maps.buttons.W3WMapButtons
24-
import com.what3words.components.compose.maps.models.W3WLocationSource
2525
import com.what3words.components.compose.maps.models.W3WGridScreenCell
26+
import com.what3words.components.compose.maps.models.W3WLocationSource
2627
import com.what3words.components.compose.maps.models.W3WMapProjection
2728
import com.what3words.components.compose.maps.models.W3WMapType
2829
import com.what3words.components.compose.maps.models.W3WMarker
@@ -265,10 +266,14 @@ internal fun W3WMapContent(
265266

266267
var bounds by remember { mutableStateOf(Rect.Zero) }
267268

268-
// Fetch current location when launch
269-
LaunchedEffect(Unit) {
270-
if (mapState.isMyLocationEnabled) {
269+
// Check if the map is initialized, use to prevent LaunchedEffect to re-run on configuration changes
270+
val isInitialized = rememberSaveable { mutableStateOf(false) }
271+
272+
// // Fetch current location when launch, but not on configuration changes
273+
LaunchedEffect(isInitialized) {
274+
if (!isInitialized.value && mapState.isMyLocationEnabled) {
271275
onMyLocationClicked.invoke()
276+
isInitialized.value = true
272277
}
273278
}
274279

lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapManager.kt

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.what3words.components.compose.maps
22

33
import android.annotation.SuppressLint
44
import android.graphics.PointF
5-
import android.util.Log
65
import androidx.annotation.RequiresPermission
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.saveable.Saver
8+
import androidx.compose.runtime.saveable.rememberSaveable
79
import androidx.compose.ui.graphics.Color
810
import com.google.android.gms.maps.model.CameraPosition
911
import com.google.maps.android.compose.CameraPositionState
@@ -63,26 +65,29 @@ import kotlinx.coroutines.withContext
6365
* through a [StateFlow]. It acts as a bridge between your application logic and the W3W map,
6466
* providing a convenient way to control the map's behavior and access its data.
6567
*
68+
* In most cases, this will be created via [rememberW3WMapManager].
69+
*
6670
* @param textDataSource An instance of [W3WTextDataSource], used for fetching what3words address information.
6771
* @param mapProvider An instance of enum [MapProvider] to define map provide: GoogleMap, MapBox.
68-
* @param mapState An optional [W3WMapState] object representing the initial state of the map. If not
72+
* @param initialMapState An optional [W3WMapState] object representing the initial state of the map. If not
6973
* provided, a default [W3WMapState] is used.
7074
*
7175
* @property mapState A read-only [StateFlow] of [W3WMapState] exposing the current state of the map.
7276
*/
7377
class W3WMapManager(
7478
private val textDataSource: W3WTextDataSource,
7579
val mapProvider: MapProvider,
76-
mapState: W3WMapState = W3WMapState(),
77-
buttonState: W3WButtonsState = W3WButtonsState(),
80+
val initialMapState: W3WMapState = W3WMapState(),
81+
private val initialButtonState: W3WButtonsState = W3WButtonsState(),
7882
private val dispatcher: CoroutineDispatcher = IO,
7983
) {
8084
private val scope = CoroutineScope(dispatcher + SupervisorJob())
8185

82-
private val _mapState: MutableStateFlow<W3WMapState> = MutableStateFlow(mapState)
86+
private val _mapState: MutableStateFlow<W3WMapState> = MutableStateFlow(initialMapState)
8387
val mapState: StateFlow<W3WMapState> = _mapState.asStateFlow()
8488

85-
private val _buttonState: MutableStateFlow<W3WButtonsState> = MutableStateFlow(buttonState)
89+
private val _buttonState: MutableStateFlow<W3WButtonsState> =
90+
MutableStateFlow(initialButtonState)
8691
val buttonState: StateFlow<W3WButtonsState> = _buttonState.asStateFlow()
8792

8893
// This flow controls when the grid calculation should be performed.
@@ -107,6 +112,13 @@ class W3WMapManager(
107112
}
108113

109114
private fun createMapboxCameraState(): W3WMapboxCameraState {
115+
val initialCameraState = initialMapState.cameraState
116+
if (initialCameraState != null) {
117+
return W3WMapboxCameraState(
118+
initialCameraState.cameraState as MapViewportState
119+
)
120+
}
121+
110122
return W3WMapboxCameraState(
111123
MapViewportState(
112124
initialCameraState = CameraState(
@@ -121,6 +133,12 @@ class W3WMapManager(
121133
}
122134

123135
private fun createGoogleCameraState(): W3WGoogleCameraState {
136+
val initialCameraState = initialMapState.cameraState
137+
if (initialCameraState != null) {
138+
return W3WGoogleCameraState(
139+
initialCameraState.cameraState as CameraPositionState
140+
)
141+
}
124142
return W3WGoogleCameraState(
125143
CameraPositionState(
126144
position = CameraPosition(
@@ -206,7 +224,6 @@ class W3WMapManager(
206224
}
207225

208226
_mapState.update {
209-
Log.d(TAG, "Update GridLines")
210227
it.copy(gridLines = newGridLines ?: W3WGridLines())
211228
}
212229
}
@@ -883,5 +900,84 @@ class W3WMapManager(
883900
companion object {
884901
const val LIST_DEFAULT_ID = "LIST_DEFAULT_ID"
885902
const val TAG = "W3WMapManager"
903+
904+
/**
905+
* The default saver implementation for [W3WMapManager].
906+
*/
907+
val Saver: Saver<W3WMapManager, *> = Saver(
908+
save = { manager: W3WMapManager ->
909+
val cameraState = manager.mapState.value.cameraState
910+
mapOf(
911+
"textDataSource" to manager.textDataSource,
912+
"mapProvider" to manager.mapProvider,
913+
"language" to manager.language,
914+
"mapState" to manager.mapState.value,
915+
"buttonState" to manager.buttonState.value,
916+
"mapConfig" to manager.mapConfig,
917+
"markersMap" to manager.markersMap.mapValues { (_, markers) ->
918+
markers.toList()
919+
},
920+
"cameraState" to when (cameraState) {
921+
is W3WGoogleCameraState -> mapOf(
922+
"type" to "google",
923+
"position" to cameraState.cameraState.position
924+
)
925+
926+
is W3WMapboxCameraState -> mapOf(
927+
"type" to "mapbox",
928+
"position" to cameraState.cameraState.cameraState
929+
)
930+
931+
else -> null
932+
}
933+
)
934+
},
935+
restore = { savedMap: Map<String, Any?> ->
936+
val mapProvider = savedMap["mapProvider"] as MapProvider
937+
val restoredCameraState = savedMap["cameraState"] as? Map<String, Any>
938+
val cameraState = when (restoredCameraState?.get("type") as? String) {
939+
"google" -> W3WGoogleCameraState(CameraPositionState(restoredCameraState["position"] as CameraPosition))
940+
"mapbox" -> W3WMapboxCameraState(MapViewportState(restoredCameraState["position"] as CameraState))
941+
else -> when (mapProvider) {
942+
MapProvider.GOOGLE_MAP -> W3WGoogleCameraState(CameraPositionState())
943+
MapProvider.MAPBOX -> W3WMapboxCameraState(MapViewportState())
944+
}
945+
}
946+
val restoredMapState =
947+
(savedMap["mapState"] as W3WMapState).copy(cameraState = cameraState)
948+
val restoredButtonState = savedMap["buttonState"] as W3WButtonsState
949+
950+
W3WMapManager(
951+
textDataSource = savedMap["textDataSource"] as W3WTextDataSource,
952+
mapProvider = mapProvider,
953+
initialMapState = restoredMapState,
954+
initialButtonState = restoredButtonState
955+
).apply {
956+
language = savedMap["language"] as W3WRFC5646Language
957+
mapConfig = savedMap["mapConfig"] as W3WMapDefaults.MapConfig?
958+
markersMap.clear()
959+
markersMap.putAll(
960+
(savedMap["markersMap"] as Map<String, List<W3WMarker>>).mapValues {
961+
it.value.toMutableList()
962+
}
963+
)
964+
}
965+
}
966+
)
886967
}
968+
}
969+
970+
/**
971+
* Create and [rememberSaveable] a [W3WMapManager] using [W3WMapManager.Saver].
972+
* [init] will be called when the [W3WMapManager] is first created to configure its
973+
* initial state.
974+
*/
975+
@Composable
976+
inline fun rememberW3WMapManager(
977+
key: String? = null,
978+
textDataSource: W3WTextDataSource,
979+
mapProvider: MapProvider,
980+
crossinline init: W3WMapManager.() -> Unit = {}
981+
): W3WMapManager = rememberSaveable(key = key, saver = W3WMapManager.Saver) {
982+
W3WMapManager(textDataSource = textDataSource, mapProvider = mapProvider).apply(init)
887983
}

0 commit comments

Comments
 (0)