Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve MarkerState API #515

Merged
merged 6 commits into from
Jun 6, 2024
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
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
Loading