Skip to content
Open
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
@@ -1,3 +1,7 @@
## 2.19.0

* Adds support for advanced markers.

## 2.18.4

* Updates Java compatibility version to 17 and minimum supported SDK version to Flutter 3.35/Dart 3.9.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ android {
dependencies {
implementation("androidx.annotation:annotation:1.9.1")
implementation("com.google.android.gms:play-services-maps:19.2.0")
implementation("com.google.maps.android:android-maps-utils:3.6.0")
implementation("com.google.maps.android:android-maps-utils:3.7.0")
androidTestImplementation("androidx.test:runner:1.7.0")
androidTestImplementation("androidx.test:rules:1.7.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.AdvancedMarkerOptions;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.view.ClusterRenderer;
import com.google.maps.android.clustering.view.DefaultAdvancedMarkersClusterRenderer;
import com.google.maps.android.clustering.view.DefaultClusterRenderer;
import com.google.maps.android.collections.MarkerManager;
import io.flutter.plugins.googlemaps.Messages.MapsCallbackApi;
import io.flutter.plugins.googlemaps.Messages.PlatformMarkerType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -29,10 +34,14 @@ class ClusterManagersController
implements GoogleMap.OnCameraIdleListener,
ClusterManager.OnClusterClickListener<MarkerBuilder> {
@NonNull private final Context context;
@NonNull private final HashMap<String, ClusterManager<MarkerBuilder>> clusterManagerIdToManager;

@VisibleForTesting @NonNull
protected final HashMap<String, ClusterManager<MarkerBuilder>> clusterManagerIdToManager;

@NonNull private final MapsCallbackApi flutterApi;
@Nullable private MarkerManager markerManager;
@Nullable private GoogleMap googleMap;
@NonNull private PlatformMarkerType markerType;

@Nullable
private ClusterManager.OnClusterItemClickListener<MarkerBuilder> clusterItemClickListener;
Expand All @@ -41,10 +50,14 @@ class ClusterManagersController
private ClusterManagersController.OnClusterItemRendered<MarkerBuilder>
clusterItemRenderedListener;

ClusterManagersController(@NonNull MapsCallbackApi flutterApi, Context context) {
ClusterManagersController(
@NonNull MapsCallbackApi flutterApi,
@NonNull Context context,
@NonNull PlatformMarkerType markerType) {
this.clusterManagerIdToManager = new HashMap<>();
this.context = context;
this.flutterApi = flutterApi;
this.markerType = markerType;
}

void init(GoogleMap googleMap, MarkerManager markerManager) {
Expand Down Expand Up @@ -89,11 +102,21 @@ void addClusterManagers(@NonNull List<Messages.PlatformClusterManager> clusterMa
void addClusterManager(String clusterManagerId) {
ClusterManager<MarkerBuilder> clusterManager =
new ClusterManager<MarkerBuilder>(context, googleMap, markerManager);
ClusterRenderer<MarkerBuilder> clusterRenderer =
new ClusterRenderer<MarkerBuilder>(context, googleMap, clusterManager, this);
initializeRenderer(clusterManager);
clusterManagerIdToManager.put(clusterManagerId, clusterManager);
}

/**
* Initializes cluster renderer based on marker type. AdvancedMarkerCluster renderer is used for
* advanced markers and MarkerClusterRenderer is used for default markers.
*/
private void initializeRenderer(ClusterManager<MarkerBuilder> clusterManager) {
final ClusterRenderer<MarkerBuilder> clusterRenderer =
markerType == PlatformMarkerType.ADVANCED_MARKER
? new AdvancedMarkerClusterRenderer<>(context, googleMap, clusterManager, this)
: new MarkerClusterRenderer<>(context, googleMap, clusterManager, this);
clusterManager.setRenderer(clusterRenderer);
initListenersForClusterManager(clusterManager, this, clusterItemClickListener);
clusterManagerIdToManager.put(clusterManagerId, clusterManager);
}

/** Removes ClusterManagers by given cluster manager IDs from the controller. */
Expand Down Expand Up @@ -195,13 +218,14 @@ public boolean onClusterClick(Cluster<MarkerBuilder> cluster) {
}

/**
* ClusterRenderer builds marker options for new markers to be rendered to the map. After cluster
* item (marker) is rendered, it is sent to the listeners for control.
* MarkerClusterRenderer builds marker options for new markers to be rendered to the map. After
* cluster item (marker) is rendered, it is sent to the listeners for control.
*/
private static class ClusterRenderer<T extends MarkerBuilder> extends DefaultClusterRenderer<T> {
@VisibleForTesting
static class MarkerClusterRenderer<T extends MarkerBuilder> extends DefaultClusterRenderer<T> {
private final ClusterManagersController clusterManagersController;

public ClusterRenderer(
public MarkerClusterRenderer(
Context context,
GoogleMap map,
ClusterManager<T> clusterManager,
Expand All @@ -225,6 +249,35 @@ protected void onClusterItemRendered(@NonNull T item, @NonNull Marker marker) {
}
}

/** AdvancedMarkerClusterRenderer is a ClusterRenderer that supports AdvancedMarkers. */
@VisibleForTesting
static class AdvancedMarkerClusterRenderer<T extends MarkerBuilder>
extends DefaultAdvancedMarkersClusterRenderer<T> {

private final ClusterManagersController clusterManagersController;

public AdvancedMarkerClusterRenderer(
Context context,
GoogleMap map,
ClusterManager<T> clusterManager,
ClusterManagersController clusterManagersController) {
super(context, map, clusterManager);
this.clusterManagersController = clusterManagersController;
}

@Override
protected void onBeforeClusterItemRendered(
@NonNull T item, @NonNull AdvancedMarkerOptions markerOptions) {
item.update(markerOptions);
}

@Override
protected void onClusterItemRendered(@NonNull T item, @NonNull Marker marker) {
super.onClusterItemRendered(item, marker);
clusterManagersController.onClusterItemRendered(item, marker);
}
}

/** Interface for handling situations where clusterManager adds new visible marker to the map. */
public interface OnClusterItemRendered<T extends ClusterItem> {
void onClusterItemRendered(@NonNull T item, @NonNull Marker marker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.AdvancedMarkerOptions;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.ButtCap;
Expand All @@ -34,6 +35,7 @@
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.PinConfig;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
import com.google.android.gms.maps.model.Tile;
Expand Down Expand Up @@ -117,6 +119,10 @@ private static BitmapDescriptor toBitmapDescriptor(
Messages.PlatformBitmapBytesMap typedBitmap = (Messages.PlatformBitmapBytesMap) bitmap;
return getBitmapFromBytes(typedBitmap, density, wrapper);
}
if (bitmap instanceof Messages.PlatformBitmapPinConfig) {
Messages.PlatformBitmapPinConfig pinConfigBitmap = (Messages.PlatformBitmapPinConfig) bitmap;
return getBitmapFromPinConfigBuilder(pinConfigBitmap, assetManager, density, wrapper);
}
throw new IllegalArgumentException("PlatformBitmap did not contain a supported subtype.");
}

Expand Down Expand Up @@ -187,6 +193,75 @@ public static BitmapDescriptor getBitmapFromBytes(
}
}

public static BitmapDescriptor getBitmapFromPinConfigBuilder(
Messages.PlatformBitmapPinConfig pinConfigBitmap,
AssetManager assetManager,
float density,
BitmapDescriptorFactoryWrapper bitmapDescriptorFactory) {
try {
final PinConfig pinConfig =
getPinConfigFromPlatformPinConfig(
pinConfigBitmap, assetManager, density, bitmapDescriptorFactory);
return bitmapDescriptorFactory.fromPinConfig(pinConfig);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to interpret pin config as a valid image.", e);
}
}

@VisibleForTesting
public static PinConfig getPinConfigFromPlatformPinConfig(
Messages.PlatformBitmapPinConfig pinConfigBitmap,
AssetManager assetManager,
float density,
BitmapDescriptorFactoryWrapper bitmapDescriptorFactory) {
final Integer backgroundColor =
pinConfigBitmap.getBackgroundColor() != null
? toInt(pinConfigBitmap.getBackgroundColor())
: null;
final Integer borderColor =
pinConfigBitmap.getBorderColor() != null ? toInt(pinConfigBitmap.getBorderColor()) : null;
final String glyphText =
pinConfigBitmap.getGlyphText() != null ? pinConfigBitmap.getGlyphText() : null;
final Integer glyphTextColor =
pinConfigBitmap.getGlyphTextColor() != null
? toInt(pinConfigBitmap.getGlyphTextColor())
: null;
final Integer glyphColor =
pinConfigBitmap.getGlyphColor() != null ? toInt(pinConfigBitmap.getGlyphColor()) : null;
final BitmapDescriptor glyphBitmapDescriptor =
pinConfigBitmap.getGlyphBitmap() != null
? toBitmapDescriptor(
pinConfigBitmap.getGlyphBitmap(), assetManager, density, bitmapDescriptorFactory)
: null;

final PinConfig.Builder pinConfigBuilder = PinConfig.builder();
if (backgroundColor != null) {
pinConfigBuilder.setBackgroundColor(backgroundColor);
}

if (borderColor != null) {
pinConfigBuilder.setBorderColor(borderColor);
}

PinConfig.Glyph glyph = null;
if (glyphText != null) {
glyph =
glyphTextColor != null
? new PinConfig.Glyph(glyphText, glyphTextColor)
: new PinConfig.Glyph(glyphText);
} else if (glyphBitmapDescriptor != null) {
glyph = new PinConfig.Glyph(glyphBitmapDescriptor);
} else if (glyphColor != null) {
glyph = new PinConfig.Glyph(glyphColor);
}

if (glyph != null) {
pinConfigBuilder.setGlyph(glyph);
}

return pinConfigBuilder.build();
}

/**
* Creates a BitmapDescriptor object from asset, using given details and density.
*
Expand Down Expand Up @@ -610,6 +685,7 @@ static void interpretMarkerOptions(
sink.setRotation(marker.getRotation().floatValue());
sink.setVisible(marker.getVisible());
sink.setZIndex(marker.getZIndex().floatValue());
sink.setCollisionBehavior(collisionBehaviorFromPigeon(marker.getCollisionBehavior()));
}

private static void interpretInfoWindowOptions(
Expand Down Expand Up @@ -648,6 +724,20 @@ static int jointTypeFromPigeon(Messages.PlatformJointType jointType) {
return JointType.DEFAULT;
}

static int collisionBehaviorFromPigeon(
Messages.PlatformMarkerCollisionBehavior collisionBehavior) {
switch (collisionBehavior) {
case REQUIRED_DISPLAY:
return AdvancedMarkerOptions.CollisionBehavior.REQUIRED;
case OPTIONAL_AND_HIDES_LOWER_PRIORITY:
return AdvancedMarkerOptions.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY;
case REQUIRED_AND_HIDES_OPTIONAL:
return AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL;
default:
return AdvancedMarkerOptions.CollisionBehavior.REQUIRED;
}
}

static String interpretPolylineOptions(
Messages.PlatformPolyline polyline,
PolylineOptionsSink sink,
Expand Down Expand Up @@ -1029,6 +1119,11 @@ public BitmapDescriptor fromAsset(String assetKey) {
public BitmapDescriptor fromBitmap(Bitmap bitmap) {
return BitmapDescriptorFactory.fromBitmap(bitmap);
}

@VisibleForTesting
public BitmapDescriptor fromPinConfig(PinConfig pinConfig) {
return BitmapDescriptorFactory.fromPinConfig(pinConfig);
}
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLngBounds;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.googlemaps.Messages.PlatformMarkerType;
import java.util.List;

class GoogleMapBuilder implements GoogleMapOptionsSink {
Expand All @@ -37,9 +38,11 @@ GoogleMapController build(
int id,
Context context,
BinaryMessenger binaryMessenger,
LifecycleProvider lifecycleProvider) {
LifecycleProvider lifecycleProvider,
PlatformMarkerType markerType) {
final GoogleMapController controller =
new GoogleMapController(id, context, binaryMessenger, lifecycleProvider, options);
new GoogleMapController(
id, context, binaryMessenger, lifecycleProvider, options, markerType);
controller.init();
controller.setMyLocationEnabled(myLocationEnabled);
controller.setMyLocationButtonEnabled(myLocationButtonEnabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapCapabilities;
import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.Polygon;
Expand All @@ -50,6 +51,7 @@
import io.flutter.plugins.googlemaps.Messages.MapsApi;
import io.flutter.plugins.googlemaps.Messages.MapsCallbackApi;
import io.flutter.plugins.googlemaps.Messages.MapsInspectorApi;
import io.flutter.plugins.googlemaps.Messages.PlatformMarkerType;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -116,7 +118,8 @@ class GoogleMapController
Context context,
BinaryMessenger binaryMessenger,
LifecycleProvider lifecycleProvider,
GoogleMapOptions options) {
GoogleMapOptions options,
PlatformMarkerType markerType) {
this.id = id;
this.context = context;
this.options = options;
Expand All @@ -128,14 +131,15 @@ class GoogleMapController
MapsInspectorApi.setUp(binaryMessenger, Integer.toString(id), this);
AssetManager assetManager = context.getAssets();
this.lifecycleProvider = lifecycleProvider;
this.clusterManagersController = new ClusterManagersController(flutterApi, context);
this.clusterManagersController = new ClusterManagersController(flutterApi, context, markerType);
this.markersController =
new MarkersController(
flutterApi,
clusterManagersController,
assetManager,
density,
new Convert.BitmapDescriptorFactoryWrapper());
new Convert.BitmapDescriptorFactoryWrapper(),
markerType);
this.polygonsController = new PolygonsController(flutterApi, density);
this.polylinesController = new PolylinesController(flutterApi, assetManager, density);
this.circlesController = new CirclesController(flutterApi, density);
Expand Down Expand Up @@ -1026,6 +1030,18 @@ public Boolean isInfoWindowShown(@NonNull String markerId) {
return lastSetStyleSucceeded;
}

@Override
public @NonNull Boolean isAdvancedMarkersAvailable() {
if (googleMap == null) {
throw new FlutterError(
"GoogleMap uninitialized",
"getMapCapabilities() called prior to map initialization",
null);
}
final MapCapabilities mapCapabilities = googleMap.getMapCapabilities();
return mapCapabilities.isAdvancedMarkersAvailable();
}

@Override
public void clearTileCache(@NonNull String tileOverlayId) {
tileOverlaysController.clearTileCache(tileOverlayId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar
builder.setInitialTileOverlays(params.getInitialTileOverlays());
builder.setInitialGroundOverlays(params.getInitialGroundOverlays());

final String cloudMapId = mapConfig.getCloudMapId();
if (cloudMapId != null) {
builder.setMapId(cloudMapId);
final String mapId = mapConfig.getMapId();
if (mapId != null && !mapId.isEmpty()) {
builder.setMapId(mapId);
}

return builder.build(id, context, binaryMessenger, lifecycleProvider);
return builder.build(
id, context, binaryMessenger, lifecycleProvider, mapConfig.getMarkerType());
}
}
Loading