Skip to content

Commit

Permalink
fix: improve MarkerState API (#515)
Browse files Browse the repository at this point in the history
* fix: improve MarkerState API

This is a non-breaking change following suggestions from @arriolac for addressing #149: #150 (comment)

This PR does not add an `onDrag` callback parameter to `Marker()`, which would be a somewhat breaking change; this functionality is not strictly necessary and I see alternatives that may be preferable.

Summary of changes:

1. Deprecate MarkerState.dragState and DragState enum. These were carried over from GoogleMap SDK; they are events that were mischaracterized as states.
2. Replace with MarkerState.isDragging boolean.
3. Clarify KDoc in several places.
4. Add several examples providing patterns for common use cases.
5. Organize Marker-related examples into their own folder.

Fixes #149

* Add an example for how to efficiently create a derived list of MarkerStates from a changing model list of marker positions by collecting results from key() composable

Rename marker examples folder to markerexamples for clarity

* Refine example

* Improve KDoc for example

* Improve KDoc for example

* Simplify and improve example

---------

Co-authored-by: Uli Bubenheimer <bubenheimer@users.noreply.github.com>
  • Loading branch information
bubenheimer and bubenheimer committed Jun 6, 2024
1 parent 9a6712f commit 3b40b92
Show file tree
Hide file tree
Showing 13 changed files with 968 additions and 22 deletions.
19 changes: 17 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@
android:name=".BasicMapActivity"
android:exported="false" />
<activity
android:name=".AdvancedMarkersActivity"
android:name=".markerexamples.AdvancedMarkersActivity"
android:exported="false"/>
<activity
android:name=".MapInColumnActivity"
android:exported="false"/>
<activity
android:name=".MarkerClusteringActivity"
android:name=".markerexamples.MarkerClusteringActivity"
android:exported="false"/>
<activity
android:name=".LocationTrackingActivity"
Expand All @@ -69,6 +69,21 @@
<activity
android:name=".RecompositionActivity"
android:exported="false"/>
<activity
android:name=".markerexamples.markerdragevents.MarkerDragEventsActivity"
android:exported="false"/>
<activity
android:name=".markerexamples.markerscollection.MarkersCollectionActivity"
android:exported="false"/>
<activity
android:name=".markerexamples.syncingdraggablemarkerwithdatamodel.SyncingDraggableMarkerWithDataModelActivity"
android:exported="false"/>
<activity
android:name=".markerexamples.updatingnodragmarkerwithdatamodel.UpdatingNoDragMarkerWithDataModelActivity"
android:exported="false"/>
<activity
android:name=".markerexamples.draggablemarkerscollectionwithpolygon.DraggableMarkersCollectionWithPolygonActivity"
android:exported="false"/>

<!-- Used by createComponentActivity() for unit testing -->
<activity android:name="androidx.activity.ComponentActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ fun GoogleMapView(
val singapore4State = rememberMarkerState(position = singapore4)

var circleCenter by remember { mutableStateOf(singapore) }
if (singaporeState.dragState == DragState.END) {
if (!singaporeState.isDragging) {
circleCenter = singaporeState.position
}

Expand Down Expand Up @@ -380,7 +380,7 @@ private fun DebugView(
Text(text = "Camera position is ${cameraPositionState.position}")
Spacer(modifier = Modifier.height(4.dp))
val dragging =
if (markerState.dragState == DragState.DRAG) "dragging" else "not dragging"
if (markerState.isDragging) "dragging" else "not dragging"
Text(text = "Marker is $dragging")
Text(text = "Marker position is ${markerState.position}")
}
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/com/google/maps/android/compose/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.google.maps.android.compose.markerexamples.AdvancedMarkersActivity
import com.google.maps.android.compose.markerexamples.MarkerClusteringActivity
import com.google.maps.android.compose.markerexamples.draggablemarkerscollectionwithpolygon.DraggableMarkersCollectionWithPolygonActivity
import com.google.maps.android.compose.markerexamples.markerdragevents.MarkerDragEventsActivity
import com.google.maps.android.compose.markerexamples.markerscollection.MarkersCollectionActivity
import com.google.maps.android.compose.markerexamples.syncingdraggablemarkerwithdatamodel.SyncingDraggableMarkerWithDataModelActivity
import com.google.maps.android.compose.markerexamples.updatingnodragmarkerwithdatamodel.UpdatingNoDragMarkerWithDataModelActivity
import com.google.maps.android.compose.theme.MapsComposeSampleTheme

class MainActivity : ComponentActivity() {
Expand Down Expand Up @@ -141,6 +148,41 @@ class MainActivity : ComponentActivity() {
}) {
Text(getString(R.string.recomposition_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(Intent(context, MarkerDragEventsActivity::class.java))
}) {
Text(getString(R.string.marker_drag_events_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(Intent(context, MarkersCollectionActivity::class.java))
}) {
Text(getString(R.string.markers_collection_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(Intent(context, SyncingDraggableMarkerWithDataModelActivity::class.java))
}) {
Text(getString(R.string.syncing_draggable_marker_with_data_model))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(Intent(context, UpdatingNoDragMarkerWithDataModelActivity::class.java))
}) {
Text(getString(R.string.updating_non_draggable_marker_with_data_model))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(Intent(context, DraggableMarkersCollectionWithPolygonActivity::class.java))
}) {
Text(getString(R.string.draggable_markers_collection_with_polygon))
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.maps.android.compose
package com.google.maps.android.compose.markerexamples


import android.R.drawable.ic_menu_myplaces
Expand All @@ -36,6 +36,12 @@ import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.PinConfig
import com.google.maps.android.compose.AdvancedMarker
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapProperties
import com.google.maps.android.compose.MapType
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerState


private const val TAG = "AdvancedMarkersActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.google.maps.android.compose
package com.google.maps.android.compose.markerexamples

import android.os.Bundle
import android.util.Log
Expand Down Expand Up @@ -40,9 +40,16 @@ import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapsComposeExperimentalApi
import com.google.maps.android.compose.MarkerInfoWindow
import com.google.maps.android.compose.clustering.Clustering
import com.google.maps.android.compose.clustering.rememberClusterManager
import com.google.maps.android.compose.clustering.rememberClusterRenderer
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerState
import com.google.maps.android.compose.singapore
import com.google.maps.android.compose.singapore2
import kotlin.random.Random

private val TAG = MarkerClusteringActivity::class.simpleName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.maps.android.compose.markerexamples.draggablemarkerscollectionwithpolygon

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Modifier
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.MarkerState
import com.google.maps.android.compose.Polygon
import com.google.maps.android.compose.defaultCameraPosition
import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.theme.MapsComposeSampleTheme

/**
* Data type representing a location.
*
* This only stores location position, for illustration,
* but could hold other data related to the location.
*/
@Immutable
private data class LocationData(val position: LatLng)

/**
* Unique, stable key for location
*/
private class LocationKey

/**
* Encapsulates mapping from data model to MarkerStates. Part of view model.
* MarkerStates are relegated to an implementation detail.
* Use new [DraggableMarkersModel] instance if data model is updated externally:
* MarkerStates are source of truth after initialization from data model.
*/
@Stable
private class DraggableMarkersModel(dataModel: Map<LocationKey, LocationData>) {
// This initializes MarkerState from our model once (model is initial source of truth)
// and never updates it from the model afterwards.
// See SyncingDraggableMarkerWithDataModelActivity for rationale.
private val markerDataMap: SnapshotStateMap<LocationKey, MarkerState> = mutableStateMapOf(
*dataModel.entries.map { (locationKey, locationData) ->
locationKey to MarkerState(locationData.position)
}.toTypedArray()
)

/** Add new marker location to model */
fun addLocation(locationData: LocationData) {
markerDataMap += LocationKey() to MarkerState(locationData.position)
}

/** Delete marker location from model */
private fun deleteLocation(locationKey: LocationKey) {
markerDataMap -= locationKey
}

/**
* Render Markers from model
*/
@Composable
fun Markers() = markerDataMap.forEach { (locationKey, markerState) ->
key(locationKey) {
LocationMarker(
markerState,
onClick = { deleteLocation(locationKey) }
)
}
}

/**
* List of functions providing current positions of Markers.
*
* Calling from composition will trigger recomposition when Markers and their positions
* change.
*/
val markerPositionsModel: List<() -> LatLng>
get() = markerDataMap.values.map { { it.position } }
}

/**
* Demonstrates how to sync a data model with a changing collection of
* draggable markers using keys, while keeping a Polygon of the marker positions in sync with
* the current marker position.
*
* The user can add a location marker to the model by clicking the map and delete a location from
* the model by clicking a marker.
*
* This example builds on top of ideas from MarkersCollectionActivity and
* SyncingDraggableMarkerWithDataModelActivity.
*/
class DraggableMarkersCollectionWithPolygonActivity : ComponentActivity() {
// Simplistic data model from repository being set from outside (should be part of view model);
// Only stores [LocationData], for illustration, but could hold additional data.
private var dataModel: Map<LocationKey, LocationData> = mapOf()
set(value) {
field = value
markersModel = DraggableMarkersModel(value)
}

private var markersModel by mutableStateOf(DraggableMarkersModel(dataModel))

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
MapsComposeSampleTheme {
GoogleMapWithLocations(
markersModel,
modifier = Modifier.fillMaxSize()
)
}
}
}
}

/**
* A GoogleMap with locations represented by markers
*/
@Composable
private fun GoogleMapWithLocations(
markersModel: DraggableMarkersModel,
modifier: Modifier = Modifier
) {
val cameraPositionState = rememberCameraPositionState { position = defaultCameraPosition }

GoogleMap(
modifier = modifier,
cameraPositionState = cameraPositionState,
onMapClick = { position -> markersModel.addLocation(LocationData(position)) }
) {
markersModel.Markers()

Polygon(markersModel::markerPositionsModel)
}
}

/**
* A draggable GoogleMap Marker representing a location on the map
*/
@Composable
private fun LocationMarker(
markerState: MarkerState,
onClick: () -> Unit
) {
Marker(
state = markerState,
draggable = true,
onClick = {
onClick()

true
}
)
}

/**
* A Polygon. Helps isolate recompositions while a Marker is being dragged.
*/
@Composable
private fun Polygon(markerPositionsModel: () -> List<() -> LatLng>) {
val movingMarkerPositions = markerPositionsModel()

if (movingMarkerPositions.isNotEmpty()) {
val markerPositions = movingMarkerPositions.map { it() }

Polygon(markerPositions)
}
}
Loading

0 comments on commit 3b40b92

Please sign in to comment.