[google_maps_flutter] add tile overlays (#3434)
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
index a1d7da0..479c100 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
+++ b/packages/google_maps_flutter/google_maps_flutter/android/build.gradle
@@ -39,6 +39,11 @@
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
index 4108a1d..f9e0ed9 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
@@ -23,6 +23,7 @@
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
+import com.google.android.gms.maps.model.Tile;
import io.flutter.view.FlutterMain;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,7 +79,8 @@
}
} else {
throw new IllegalArgumentException(
- "fromBytes should have exactly one argument, the bytes. Got: " + data.size());
+ "fromBytes should have exactly one argument, interpretTileOverlayOptions the bytes. Got: "
+ + data.size());
}
}
@@ -200,6 +202,20 @@
return data;
}
+ static Map<String, Object> tileOverlayArgumentsToJson(
+ String tileOverlayId, int x, int y, int zoom) {
+
+ if (tileOverlayId == null) {
+ return null;
+ }
+ final Map<String, Object> data = new HashMap<>(4);
+ data.put("tileOverlayId", tileOverlayId);
+ data.put("x", x);
+ data.put("y", y);
+ data.put("zoom", zoom);
+ return data;
+ }
+
static Object latLngToJson(LatLng latLng) {
return Arrays.asList(latLng.latitude, latLng.longitude);
}
@@ -645,4 +661,39 @@
throw new IllegalArgumentException("Cannot interpret " + o + " as Cap");
}
}
+
+ static String interpretTileOverlayOptions(Map<String, ?> data, TileOverlaySink sink) {
+ final Object fadeIn = data.get("fadeIn");
+ if (fadeIn != null) {
+ sink.setFadeIn(toBoolean(fadeIn));
+ }
+ final Object transparency = data.get("transparency");
+ if (transparency != null) {
+ sink.setTransparency(toFloat(transparency));
+ }
+ final Object zIndex = data.get("zIndex");
+ if (zIndex != null) {
+ sink.setZIndex(toFloat(zIndex));
+ }
+ final Object visible = data.get("visible");
+ if (visible != null) {
+ sink.setVisible(toBoolean(visible));
+ }
+ final String tileOverlayId = (String) data.get("tileOverlayId");
+ if (tileOverlayId == null) {
+ throw new IllegalArgumentException("tileOverlayId was null");
+ } else {
+ return tileOverlayId;
+ }
+ }
+
+ static Tile interpretTile(Map<String, ?> data) {
+ int width = toInt(data.get("width"));
+ int height = toInt(data.get("height"));
+ byte[] dataArray = null;
+ if (data.get("data") != null) {
+ dataArray = (byte[]) data.get("data");
+ }
+ return new Tile(width, height, dataArray);
+ }
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
index 93a3c3e..6d5c8c6 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
@@ -10,6 +10,8 @@
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLngBounds;
import io.flutter.plugin.common.BinaryMessenger;
+import java.util.List;
+import java.util.Map;
class GoogleMapBuilder implements GoogleMapOptionsSink {
private final GoogleMapOptions options = new GoogleMapOptions();
@@ -23,6 +25,7 @@
private Object initialPolygons;
private Object initialPolylines;
private Object initialCircles;
+ private List<Map<String, ?>> initialTileOverlays;
private Rect padding = new Rect(0, 0, 0, 0);
GoogleMapController build(
@@ -44,6 +47,7 @@
controller.setInitialPolylines(initialPolylines);
controller.setInitialCircles(initialCircles);
controller.setPadding(padding.top, padding.left, padding.bottom, padding.right);
+ controller.setInitialTileOverlays(initialTileOverlays);
return controller;
}
@@ -165,4 +169,9 @@
public void setInitialCircles(Object initialCircles) {
this.initialCircles = initialCircles;
}
+
+ @Override
+ public void setInitialTileOverlays(List<Map<String, ?>> initialTileOverlays) {
+ this.initialTileOverlays = initialTileOverlays;
+ }
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
index f6b8c3f..7db65c5 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
@@ -76,10 +76,12 @@
private final PolygonsController polygonsController;
private final PolylinesController polylinesController;
private final CirclesController circlesController;
+ private final TileOverlaysController tileOverlaysController;
private List<Object> initialMarkers;
private List<Object> initialPolygons;
private List<Object> initialPolylines;
private List<Object> initialCircles;
+ private List<Map<String, ?>> initialTileOverlays;
GoogleMapController(
int id,
@@ -99,6 +101,7 @@
this.polygonsController = new PolygonsController(methodChannel, density);
this.polylinesController = new PolylinesController(methodChannel, density);
this.circlesController = new CirclesController(methodChannel, density);
+ this.tileOverlaysController = new TileOverlaysController(methodChannel);
}
@Override
@@ -140,10 +143,12 @@
polygonsController.setGoogleMap(googleMap);
polylinesController.setGoogleMap(googleMap);
circlesController.setGoogleMap(googleMap);
+ tileOverlaysController.setGoogleMap(googleMap);
updateInitialMarkers();
updateInitialPolygons();
updateInitialPolylines();
updateInitialCircles();
+ updateInitialTileOverlays();
}
@Override
@@ -385,6 +390,30 @@
result.success(mapStyleResult);
break;
}
+ case "tileOverlays#update":
+ {
+ List<Map<String, ?>> tileOverlaysToAdd = call.argument("tileOverlaysToAdd");
+ tileOverlaysController.addTileOverlays(tileOverlaysToAdd);
+ List<Map<String, ?>> tileOverlaysToChange = call.argument("tileOverlaysToChange");
+ tileOverlaysController.changeTileOverlays(tileOverlaysToChange);
+ List<String> tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove");
+ tileOverlaysController.removeTileOverlays(tileOverlaysToRemove);
+ result.success(null);
+ break;
+ }
+ case "tileOverlays#clearTileCache":
+ {
+ String tileOverlayId = call.argument("tileOverlayId");
+ tileOverlaysController.clearTileCache(tileOverlayId);
+ result.success(null);
+ break;
+ }
+ case "map#getTileOverlayInfo":
+ {
+ String tileOverlayId = call.argument("tileOverlayId");
+ result.success(tileOverlaysController.getTileOverlayInfo(tileOverlayId));
+ break;
+ }
default:
result.notImplemented();
}
@@ -732,6 +761,18 @@
circlesController.addCircles(initialCircles);
}
+ @Override
+ public void setInitialTileOverlays(List<Map<String, ?>> initialTileOverlays) {
+ this.initialTileOverlays = initialTileOverlays;
+ if (googleMap != null) {
+ updateInitialTileOverlays();
+ }
+ }
+
+ private void updateInitialTileOverlays() {
+ tileOverlaysController.addTileOverlays(initialTileOverlays);
+ }
+
@SuppressLint("MissingPermission")
private void updateMyLocationSettings() {
if (hasLocationPermission()) {
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
index e56adbb..bf9188f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
@@ -10,6 +10,7 @@
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
+import java.util.List;
import java.util.Map;
public class GoogleMapFactory extends PlatformViewFactory {
@@ -46,6 +47,9 @@
if (params.containsKey("circlesToAdd")) {
builder.setInitialCircles(params.get("circlesToAdd"));
}
+ if (params.containsKey("tileOverlaysToAdd")) {
+ builder.setInitialTileOverlays((List<Map<String, ?>>) params.get("tileOverlaysToAdd"));
+ }
return builder.build(id, context, binaryMessenger, lifecycleProvider);
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
index 9e6fa2a..03377d4 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
@@ -5,6 +5,8 @@
package io.flutter.plugins.googlemaps;
import com.google.android.gms.maps.model.LatLngBounds;
+import java.util.List;
+import java.util.Map;
/** Receiver of GoogleMap configuration options. */
interface GoogleMapOptionsSink {
@@ -51,4 +53,6 @@
void setInitialPolylines(Object initialPolylines);
void setInitialCircles(Object initialCircles);
+
+ void setInitialTileOverlays(List<Map<String, ?>> initialTileOverlays);
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java
new file mode 100644
index 0000000..1b55933
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java
@@ -0,0 +1,46 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import com.google.android.gms.maps.model.TileOverlayOptions;
+import com.google.android.gms.maps.model.TileProvider;
+
+class TileOverlayBuilder implements TileOverlaySink {
+
+ private final TileOverlayOptions tileOverlayOptions;
+
+ TileOverlayBuilder() {
+ this.tileOverlayOptions = new TileOverlayOptions();
+ }
+
+ TileOverlayOptions build() {
+ return tileOverlayOptions;
+ }
+
+ @Override
+ public void setFadeIn(boolean fadeIn) {
+ tileOverlayOptions.fadeIn(fadeIn);
+ }
+
+ @Override
+ public void setTransparency(float transparency) {
+ tileOverlayOptions.transparency(transparency);
+ }
+
+ @Override
+ public void setZIndex(float zIndex) {
+ tileOverlayOptions.zIndex(zIndex);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ tileOverlayOptions.visible(visible);
+ }
+
+ @Override
+ public void setTileProvider(TileProvider tileProvider) {
+ tileOverlayOptions.tileProvider(tileProvider);
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java
new file mode 100644
index 0000000..1204bcd
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java
@@ -0,0 +1,62 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import com.google.android.gms.maps.model.TileOverlay;
+import com.google.android.gms.maps.model.TileProvider;
+import java.util.HashMap;
+import java.util.Map;
+
+class TileOverlayController implements TileOverlaySink {
+
+ private final TileOverlay tileOverlay;
+
+ TileOverlayController(TileOverlay tileOverlay) {
+ this.tileOverlay = tileOverlay;
+ }
+
+ void remove() {
+ tileOverlay.remove();
+ }
+
+ void clearTileCache() {
+ tileOverlay.clearTileCache();
+ }
+
+ Map<String, Object> getTileOverlayInfo() {
+ Map<String, Object> tileOverlayInfo = new HashMap<>();
+ tileOverlayInfo.put("fadeIn", tileOverlay.getFadeIn());
+ tileOverlayInfo.put("transparency", tileOverlay.getTransparency());
+ tileOverlayInfo.put("id", tileOverlay.getId());
+ tileOverlayInfo.put("zIndex", tileOverlay.getZIndex());
+ tileOverlayInfo.put("visible", tileOverlay.isVisible());
+ return tileOverlayInfo;
+ }
+
+ @Override
+ public void setFadeIn(boolean fadeIn) {
+ tileOverlay.setFadeIn(fadeIn);
+ }
+
+ @Override
+ public void setTransparency(float transparency) {
+ tileOverlay.setTransparency(transparency);
+ }
+
+ @Override
+ public void setZIndex(float zIndex) {
+ tileOverlay.setZIndex(zIndex);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ tileOverlay.setVisible(visible);
+ }
+
+ @Override
+ public void setTileProvider(TileProvider tileProvider) {
+ // You can not change tile provider after creation
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java
new file mode 100644
index 0000000..fd611d7
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import com.google.android.gms.maps.model.TileProvider;
+
+/** Receiver of TileOverlayOptions configuration. */
+interface TileOverlaySink {
+ void setFadeIn(boolean fadeIn);
+
+ void setTransparency(float transparency);
+
+ void setZIndex(float zIndex);
+
+ void setVisible(boolean visible);
+
+ void setTileProvider(TileProvider tileProvider);
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java
new file mode 100644
index 0000000..cd583e2
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java
@@ -0,0 +1,120 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.TileOverlay;
+import com.google.android.gms.maps.model.TileOverlayOptions;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class TileOverlaysController {
+
+ private final Map<String, TileOverlayController> tileOverlayIdToController;
+ private final MethodChannel methodChannel;
+ private GoogleMap googleMap;
+
+ TileOverlaysController(MethodChannel methodChannel) {
+ this.tileOverlayIdToController = new HashMap<>();
+ this.methodChannel = methodChannel;
+ }
+
+ void setGoogleMap(GoogleMap googleMap) {
+ this.googleMap = googleMap;
+ }
+
+ void addTileOverlays(List<Map<String, ?>> tileOverlaysToAdd) {
+ if (tileOverlaysToAdd == null) {
+ return;
+ }
+ for (Map<String, ?> tileOverlayToAdd : tileOverlaysToAdd) {
+ addTileOverlay(tileOverlayToAdd);
+ }
+ }
+
+ void changeTileOverlays(List<Map<String, ?>> tileOverlaysToChange) {
+ if (tileOverlaysToChange == null) {
+ return;
+ }
+ for (Map<String, ?> tileOverlayToChange : tileOverlaysToChange) {
+ changeTileOverlay(tileOverlayToChange);
+ }
+ }
+
+ void removeTileOverlays(List<String> tileOverlayIdsToRemove) {
+ if (tileOverlayIdsToRemove == null) {
+ return;
+ }
+ for (String tileOverlayId : tileOverlayIdsToRemove) {
+ if (tileOverlayId == null) {
+ continue;
+ }
+ removeTileOverlay(tileOverlayId);
+ }
+ }
+
+ void clearTileCache(String tileOverlayId) {
+ if (tileOverlayId == null) {
+ return;
+ }
+ TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId);
+ if (tileOverlayController != null) {
+ tileOverlayController.clearTileCache();
+ }
+ }
+
+ Map<String, Object> getTileOverlayInfo(String tileOverlayId) {
+ if (tileOverlayId == null) {
+ return null;
+ }
+ TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId);
+ if (tileOverlayController == null) {
+ return null;
+ }
+ return tileOverlayController.getTileOverlayInfo();
+ }
+
+ private void addTileOverlay(Map<String, ?> tileOverlayOptions) {
+ if (tileOverlayOptions == null) {
+ return;
+ }
+ TileOverlayBuilder tileOverlayOptionsBuilder = new TileOverlayBuilder();
+ String tileOverlayId =
+ Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayOptionsBuilder);
+ TileProviderController tileProviderController =
+ new TileProviderController(methodChannel, tileOverlayId);
+ tileOverlayOptionsBuilder.setTileProvider(tileProviderController);
+ TileOverlayOptions options = tileOverlayOptionsBuilder.build();
+ TileOverlay tileOverlay = googleMap.addTileOverlay(options);
+ TileOverlayController tileOverlayController = new TileOverlayController(tileOverlay);
+ tileOverlayIdToController.put(tileOverlayId, tileOverlayController);
+ }
+
+ private void changeTileOverlay(Map<String, ?> tileOverlayOptions) {
+ if (tileOverlayOptions == null) {
+ return;
+ }
+ String tileOverlayId = getTileOverlayId(tileOverlayOptions);
+ TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId);
+ if (tileOverlayController != null) {
+ Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayController);
+ }
+ }
+
+ private void removeTileOverlay(String tileOverlayId) {
+ TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId);
+ if (tileOverlayController != null) {
+ tileOverlayController.remove();
+ tileOverlayIdToController.remove(tileOverlayId);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static String getTileOverlayId(Map<String, ?> tileOverlay) {
+ return (String) tileOverlay.get("tileOverlayId");
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java
new file mode 100644
index 0000000..f28118c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java
@@ -0,0 +1,100 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.googlemaps;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.android.gms.maps.model.Tile;
+import com.google.android.gms.maps.model.TileProvider;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+class TileProviderController implements TileProvider {
+
+ private static final String TAG = "TileProviderController";
+
+ private final String tileOverlayId;
+ private final MethodChannel methodChannel;
+ private final Handler handler = new Handler(Looper.getMainLooper());
+
+ TileProviderController(MethodChannel methodChannel, String tileOverlayId) {
+ this.tileOverlayId = tileOverlayId;
+ this.methodChannel = methodChannel;
+ }
+
+ @Override
+ public Tile getTile(final int x, final int y, final int zoom) {
+ Worker worker = new Worker(x, y, zoom);
+ return worker.getTile();
+ }
+
+ private final class Worker implements MethodChannel.Result {
+
+ private final CountDownLatch countDownLatch = new CountDownLatch(1);
+ private final int x;
+ private final int y;
+ private final int zoom;
+ private Map<String, ?> result;
+
+ Worker(int x, int y, int zoom) {
+ this.x = x;
+ this.y = y;
+ this.zoom = zoom;
+ }
+
+ @NonNull
+ Tile getTile() {
+ handler.post(
+ () ->
+ methodChannel.invokeMethod(
+ "tileOverlay#getTile",
+ Convert.tileOverlayArgumentsToJson(tileOverlayId, x, y, zoom),
+ this));
+ try {
+ // Because `methodChannel.invokeMethod` is async, we use a `countDownLatch` make it synchronized.
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(
+ TAG,
+ String.format("countDownLatch: can't get tile: x = %d, y= %d, zoom = %d", x, y, zoom),
+ e);
+ return TileProvider.NO_TILE;
+ }
+ try {
+ return Convert.interpretTile(result);
+ } catch (Exception e) {
+ Log.e(TAG, "Can't parse tile data", e);
+ return TileProvider.NO_TILE;
+ }
+ }
+
+ @Override
+ public void success(Object data) {
+ result = (Map<String, ?>) data;
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void error(String errorCode, String errorMessage, Object data) {
+ Log.e(
+ TAG,
+ String.format(
+ "Can't get tile: errorCode = %s, errorMessage = %s, date = %s",
+ errorCode, errorCode, data));
+ result = null;
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void notImplemented() {
+ Log.e(TAG, "Can't get tile: notImplemented");
+ result = null;
+ countDownLatch.countDown();
+ }
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
index 2fcfec1..0f2dafb 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
@@ -76,4 +76,11 @@
Future<Uint8List> takeSnapshot() async {
return await _channel.invokeMethod<Uint8List>('map#takeSnapshot');
}
+
+ Future<Map<String, dynamic>> getTileOverlayInfo(String id) async {
+ return await _channel.invokeMapMethod<String, dynamic>(
+ 'map#getTileOverlayInfo', <String, String>{
+ 'tileOverlayId': id,
+ });
+ }
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
index 2a5bf80..557337f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
+import 'dart:ui' as ui;
import 'package:integration_test/integration_test.dart';
import 'package:flutter/material.dart';
@@ -987,4 +988,247 @@
// TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled.
// https://github.com/flutter/flutter/issues/57057
skip: Platform.isAndroid);
+
+ testWidgets(
+ 'set tileOverlay correctly',
+ (WidgetTester tester) async {
+ Completer<GoogleMapInspector> inspectorCompleter =
+ Completer<GoogleMapInspector>();
+ final TileOverlay tileOverlay1 = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_1'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 2,
+ visible: true,
+ transparency: 0.2,
+ fadeIn: true,
+ );
+
+ final TileOverlay tileOverlay2 = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_2'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 1,
+ visible: false,
+ transparency: 0.3,
+ fadeIn: false,
+ );
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ initialCameraPosition: _kInitialCameraPosition,
+ tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+ onMapCreated: (GoogleMapController controller) {
+ final GoogleMapInspector inspector =
+ // ignore: invalid_use_of_visible_for_testing_member
+ GoogleMapInspector(controller.channel);
+ inspectorCompleter.complete(inspector);
+ },
+ ),
+ ),
+ );
+ await tester.pumpAndSettle(const Duration(seconds: 3));
+
+ final GoogleMapInspector inspector = await inspectorCompleter.future;
+
+ Map<String, dynamic> tileOverlayInfo1 =
+ await inspector.getTileOverlayInfo('tile_overlay_1');
+ Map<String, dynamic> tileOverlayInfo2 =
+ await inspector.getTileOverlayInfo('tile_overlay_2');
+
+ expect(tileOverlayInfo1['visible'], isTrue);
+ expect(tileOverlayInfo1['fadeIn'], isTrue);
+ expect(tileOverlayInfo1['transparency'],
+ moreOrLessEquals(0.2, epsilon: 0.001));
+ expect(tileOverlayInfo1['zIndex'], 2);
+
+ expect(tileOverlayInfo2['visible'], isFalse);
+ expect(tileOverlayInfo2['fadeIn'], isFalse);
+ expect(tileOverlayInfo2['transparency'],
+ moreOrLessEquals(0.3, epsilon: 0.001));
+ expect(tileOverlayInfo2['zIndex'], 1);
+ },
+ );
+
+ testWidgets(
+ 'update tileOverlays correctly',
+ (WidgetTester tester) async {
+ Completer<GoogleMapInspector> inspectorCompleter =
+ Completer<GoogleMapInspector>();
+ final Key key = GlobalKey();
+ final TileOverlay tileOverlay1 = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_1'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 2,
+ visible: true,
+ transparency: 0.2,
+ fadeIn: true,
+ );
+
+ final TileOverlay tileOverlay2 = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_2'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 3,
+ visible: true,
+ transparency: 0.5,
+ fadeIn: true,
+ );
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ key: key,
+ initialCameraPosition: _kInitialCameraPosition,
+ tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+ onMapCreated: (GoogleMapController controller) {
+ final GoogleMapInspector inspector =
+ // ignore: invalid_use_of_visible_for_testing_member
+ GoogleMapInspector(controller.channel);
+ inspectorCompleter.complete(inspector);
+ },
+ ),
+ ),
+ );
+
+ final GoogleMapInspector inspector = await inspectorCompleter.future;
+
+ final TileOverlay tileOverlay1New = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_1'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 1,
+ visible: false,
+ transparency: 0.3,
+ fadeIn: false,
+ );
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ key: key,
+ initialCameraPosition: _kInitialCameraPosition,
+ tileOverlays: <TileOverlay>{tileOverlay1New},
+ onMapCreated: (GoogleMapController controller) {
+ fail('update: OnMapCreated should get called only once.');
+ },
+ ),
+ ),
+ );
+
+ await tester.pumpAndSettle(const Duration(seconds: 3));
+
+ Map<String, dynamic> tileOverlayInfo1 =
+ await inspector.getTileOverlayInfo('tile_overlay_1');
+ Map<String, dynamic> tileOverlayInfo2 =
+ await inspector.getTileOverlayInfo('tile_overlay_2');
+
+ expect(tileOverlayInfo1['visible'], isFalse);
+ expect(tileOverlayInfo1['fadeIn'], isFalse);
+ expect(tileOverlayInfo1['transparency'],
+ moreOrLessEquals(0.3, epsilon: 0.001));
+ expect(tileOverlayInfo1['zIndex'], 1);
+
+ expect(tileOverlayInfo2, isNull);
+ },
+ );
+
+ testWidgets(
+ 'remove tileOverlays correctly',
+ (WidgetTester tester) async {
+ Completer<GoogleMapInspector> inspectorCompleter =
+ Completer<GoogleMapInspector>();
+ final Key key = GlobalKey();
+ final TileOverlay tileOverlay1 = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_1'),
+ tileProvider: _DebugTileProvider(),
+ zIndex: 2,
+ visible: true,
+ transparency: 0.2,
+ fadeIn: true,
+ );
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ key: key,
+ initialCameraPosition: _kInitialCameraPosition,
+ tileOverlays: <TileOverlay>{tileOverlay1},
+ onMapCreated: (GoogleMapController controller) {
+ final GoogleMapInspector inspector =
+ // ignore: invalid_use_of_visible_for_testing_member
+ GoogleMapInspector(controller.channel);
+ inspectorCompleter.complete(inspector);
+ },
+ ),
+ ),
+ );
+
+ final GoogleMapInspector inspector = await inspectorCompleter.future;
+
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ key: key,
+ initialCameraPosition: _kInitialCameraPosition,
+ onMapCreated: (GoogleMapController controller) {
+ fail('OnMapCreated should get called only once.');
+ },
+ ),
+ ),
+ );
+
+ await tester.pumpAndSettle(const Duration(seconds: 3));
+ Map<String, dynamic> tileOverlayInfo1 =
+ await inspector.getTileOverlayInfo('tile_overlay_1');
+
+ expect(tileOverlayInfo1, isNull);
+ },
+ );
+}
+
+class _DebugTileProvider implements TileProvider {
+ _DebugTileProvider() {
+ boxPaint.isAntiAlias = true;
+ boxPaint.color = Colors.blue;
+ boxPaint.strokeWidth = 2.0;
+ boxPaint.style = PaintingStyle.stroke;
+ }
+
+ static const int width = 100;
+ static const int height = 100;
+ static final Paint boxPaint = Paint();
+ static final TextStyle textStyle = TextStyle(
+ color: Colors.red,
+ fontSize: 20,
+ );
+
+ @override
+ Future<Tile> getTile(int x, int y, int zoom) async {
+ final ui.PictureRecorder recorder = ui.PictureRecorder();
+ final Canvas canvas = Canvas(recorder);
+ final TextSpan textSpan = TextSpan(
+ text: "$x,$y",
+ style: textStyle,
+ );
+ final TextPainter textPainter = TextPainter(
+ text: textSpan,
+ textDirection: TextDirection.ltr,
+ );
+ textPainter.layout(
+ minWidth: 0.0,
+ maxWidth: width.toDouble(),
+ );
+ final Offset offset = const Offset(0, 0);
+ textPainter.paint(canvas, offset);
+ canvas.drawRect(
+ Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+ final ui.Picture picture = recorder.endRecording();
+ final Uint8List byteData = await picture
+ .toImage(width, height)
+ .then((ui.Image image) =>
+ image.toByteData(format: ui.ImageByteFormat.png))
+ .then((ByteData byteData) => byteData.buffer.asUint8List());
+ return Tile(width, height, byteData);
+ }
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
index 13763ed..b795040 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
@@ -20,6 +20,7 @@
import 'place_polyline.dart';
import 'scrolling_map.dart';
import 'snapshot.dart';
+import 'tile_overlay.dart';
final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
MapUiPage(),
@@ -36,6 +37,7 @@
PaddingPage(),
SnapshotPage(),
LiteModePage(),
+ TileOverlayPage(),
];
class MapsDemo extends StatelessWidget {
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart
new file mode 100644
index 0000000..354fa06
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart
@@ -0,0 +1,151 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter/google_maps_flutter.dart';
+
+import 'page.dart';
+
+class TileOverlayPage extends GoogleMapExampleAppPage {
+ TileOverlayPage() : super(const Icon(Icons.map), 'Tile overlay');
+
+ @override
+ Widget build(BuildContext context) {
+ return const TileOverlayBody();
+ }
+}
+
+class TileOverlayBody extends StatefulWidget {
+ const TileOverlayBody();
+
+ @override
+ State<StatefulWidget> createState() => TileOverlayBodyState();
+}
+
+class TileOverlayBodyState extends State<TileOverlayBody> {
+ TileOverlayBodyState();
+
+ GoogleMapController controller;
+ TileOverlay _tileOverlay;
+
+ void _onMapCreated(GoogleMapController controller) {
+ this.controller = controller;
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ void _removeTileOverlay() {
+ setState(() {
+ _tileOverlay = null;
+ });
+ }
+
+ void _addTileOverlay() {
+ final TileOverlay tileOverlay = TileOverlay(
+ tileOverlayId: TileOverlayId('tile_overlay_1'),
+ tileProvider: _DebugTileProvider(),
+ );
+ setState(() {
+ _tileOverlay = tileOverlay;
+ });
+ }
+
+ void _clearTileCache() {
+ if (_tileOverlay != null && controller != null) {
+ controller.clearTileCache(_tileOverlay.tileOverlayId);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: <Widget>[
+ Center(
+ child: SizedBox(
+ width: 350.0,
+ height: 300.0,
+ child: GoogleMap(
+ initialCameraPosition: const CameraPosition(
+ target: LatLng(59.935460, 30.325177),
+ zoom: 7.0,
+ ),
+ tileOverlays:
+ _tileOverlay != null ? <TileOverlay>{_tileOverlay} : null,
+ onMapCreated: _onMapCreated,
+ ),
+ ),
+ ),
+ TextButton(
+ child: const Text('Add tile overlay'),
+ onPressed: _addTileOverlay,
+ ),
+ TextButton(
+ child: const Text('Remove tile overlay'),
+ onPressed: _removeTileOverlay,
+ ),
+ TextButton(
+ child: const Text('Clear tile cache'),
+ onPressed: _clearTileCache,
+ ),
+ ],
+ );
+ }
+}
+
+class _DebugTileProvider implements TileProvider {
+ _DebugTileProvider() {
+ boxPaint.isAntiAlias = true;
+ boxPaint.color = Colors.blue;
+ boxPaint.strokeWidth = 2.0;
+ boxPaint.style = PaintingStyle.stroke;
+ }
+
+ static const int width = 100;
+ static const int height = 100;
+ static final Paint boxPaint = Paint();
+ static final TextStyle textStyle = TextStyle(
+ color: Colors.red,
+ fontSize: 20,
+ );
+
+ @override
+ Future<Tile> getTile(int x, int y, int zoom) async {
+ final ui.PictureRecorder recorder = ui.PictureRecorder();
+ final Canvas canvas = Canvas(recorder);
+ final TextSpan textSpan = TextSpan(
+ text: '$x,$y',
+ style: textStyle,
+ );
+ final TextPainter textPainter = TextPainter(
+ text: textSpan,
+ textDirection: TextDirection.ltr,
+ );
+ textPainter.layout(
+ minWidth: 0.0,
+ maxWidth: width.toDouble(),
+ );
+ final Offset offset = const Offset(0, 0);
+ textPainter.paint(canvas, offset);
+ canvas.drawRect(
+ Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+ final ui.Picture picture = recorder.endRecording();
+ final Uint8List byteData = await picture
+ .toImage(width, height)
+ .then((ui.Image image) =>
+ image.toByteData(format: ui.ImageByteFormat.png))
+ .then((ByteData byteData) => byteData.buffer.asUint8List());
+ return Tile(width, height, byteData);
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
new file mode 100644
index 0000000..f84ad7c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <GoogleMaps/GoogleMaps.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+// Defines map UI options writable from Flutter.
+@protocol FLTGoogleMapTileOverlayOptionsSink
+- (void)setFadeIn:(BOOL)fadeIn;
+- (void)setTransparency:(float)transparency;
+- (void)setZIndex:(int)zIndex;
+- (void)setVisible:(BOOL)visible;
+- (void)setTileSize:(NSInteger)tileSize;
+@end
+
+@interface FLTGoogleMapTileOverlayController : NSObject <FLTGoogleMapTileOverlayOptionsSink>
+- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView;
+- (void)removeTileOverlay;
+- (void)clearTileCache;
+- (NSDictionary *)getTileOverlayInfo;
+@end
+
+@interface FLTTileProviderController : GMSTileLayer
+@property(copy, nonatomic, readonly) NSString *tileOverlayId;
+- (instancetype)init:(FlutterMethodChannel *)methodChannel tileOverlayId:(NSString *)tileOverlayId;
+@end
+
+@interface FLTTileOverlaysController : NSObject
+- (instancetype)init:(FlutterMethodChannel *)methodChannel
+ mapView:(GMSMapView *)mapView
+ registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
+- (void)addTileOverlays:(NSArray *)tileOverlaysToAdd;
+- (void)changeTileOverlays:(NSArray *)tileOverlaysToChange;
+- (void)removeTileOverlayIds:(NSArray *)tileOverlayIdsToRemove;
+- (void)clearTileCache:(NSString *)tileOverlayId;
+- (nullable NSDictionary *)getTileOverlayInfo:(NSString *)tileverlayId;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
new file mode 100644
index 0000000..7fbd7c5
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
@@ -0,0 +1,234 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FLTGoogleMapTileOverlayController.h"
+#import "JsonConversions.h"
+
+static void InterpretTileOverlayOptions(NSDictionary* data,
+ id<FLTGoogleMapTileOverlayOptionsSink> sink,
+ NSObject<FlutterPluginRegistrar>* registrar) {
+ NSNumber* visible = data[@"visible"];
+ if (visible != nil) {
+ [sink setVisible:visible.boolValue];
+ }
+
+ NSNumber* transparency = data[@"transparency"];
+ if (transparency != nil) {
+ [sink setTransparency:transparency.floatValue];
+ }
+
+ NSNumber* zIndex = data[@"zIndex"];
+ if (zIndex != nil) {
+ [sink setZIndex:zIndex.intValue];
+ }
+
+ NSNumber* fadeIn = data[@"fadeIn"];
+ if (fadeIn != nil) {
+ [sink setFadeIn:fadeIn.boolValue];
+ }
+
+ NSNumber* tileSize = data[@"tileSize"];
+ if (tileSize != nil) {
+ [sink setTileSize:tileSize.integerValue];
+ }
+}
+
+@interface FLTGoogleMapTileOverlayController ()
+
+@property(strong, nonatomic) GMSTileLayer* layer;
+@property(weak, nonatomic) GMSMapView* mapView;
+
+@end
+
+@implementation FLTGoogleMapTileOverlayController
+
+- (instancetype)initWithTileLayer:(GMSTileLayer*)tileLayer mapView:(GMSMapView*)mapView {
+ self = [super init];
+ if (self) {
+ self.layer = tileLayer;
+ self.mapView = mapView;
+ }
+ return self;
+}
+
+- (void)removeTileOverlay {
+ self.layer.map = nil;
+}
+
+- (void)clearTileCache {
+ [self.layer clearTileCache];
+}
+
+- (NSDictionary*)getTileOverlayInfo {
+ NSMutableDictionary* info = [[NSMutableDictionary alloc] init];
+ BOOL visible = self.layer.map != nil;
+ info[@"visible"] = @(visible);
+ info[@"fadeIn"] = @(self.layer.fadeIn);
+ float transparency = 1.0 - self.layer.opacity;
+ info[@"transparency"] = @(transparency);
+ info[@"zIndex"] = @(self.layer.zIndex);
+ return info;
+}
+
+#pragma mark - FLTGoogleMapTileOverlayOptionsSink methods
+
+- (void)setFadeIn:(BOOL)fadeIn {
+ self.layer.fadeIn = fadeIn;
+}
+
+- (void)setTransparency:(float)transparency {
+ float opacity = 1.0 - transparency;
+ self.layer.opacity = opacity;
+}
+
+- (void)setVisible:(BOOL)visible {
+ self.layer.map = visible ? self.mapView : nil;
+}
+
+- (void)setZIndex:(int)zIndex {
+ self.layer.zIndex = zIndex;
+}
+
+- (void)setTileSize:(NSInteger)tileSize {
+ self.layer.tileSize = tileSize;
+}
+@end
+
+@interface FLTTileProviderController ()
+
+@property(weak, nonatomic) FlutterMethodChannel* methodChannel;
+@property(copy, nonatomic, readwrite) NSString* tileOverlayId;
+
+@end
+
+@implementation FLTTileProviderController
+
+- (instancetype)init:(FlutterMethodChannel*)methodChannel tileOverlayId:(NSString*)tileOverlayId {
+ self = [super init];
+ if (self) {
+ self.methodChannel = methodChannel;
+ self.tileOverlayId = tileOverlayId;
+ }
+ return self;
+}
+
+#pragma mark - GMSTileLayer method
+
+- (void)requestTileForX:(NSUInteger)x
+ y:(NSUInteger)y
+ zoom:(NSUInteger)zoom
+ receiver:(id<GMSTileReceiver>)receiver {
+ [self.methodChannel
+ invokeMethod:@"tileOverlay#getTile"
+ arguments:@{
+ @"tileOverlayId" : self.tileOverlayId,
+ @"x" : @(x),
+ @"y" : @(y),
+ @"zoom" : @(zoom)
+ }
+ result:^(id _Nullable result) {
+ UIImage* tileImage;
+ if ([result isKindOfClass:[NSDictionary class]]) {
+ FlutterStandardTypedData* typedData = (FlutterStandardTypedData*)result[@"data"];
+ if (typedData == nil) {
+ tileImage = kGMSTileLayerNoTile;
+ } else {
+ tileImage = [UIImage imageWithData:typedData.data];
+ }
+ } else {
+ if ([result isKindOfClass:[FlutterError class]]) {
+ FlutterError* error = (FlutterError*)result;
+ NSLog(@"Can't get tile: errorCode = %@, errorMessage = %@, details = %@",
+ [error code], [error message], [error details]);
+ }
+ if ([result isKindOfClass:[FlutterMethodNotImplemented class]]) {
+ NSLog(@"Can't get tile: notImplemented");
+ }
+ tileImage = kGMSTileLayerNoTile;
+ }
+
+ [receiver receiveTileWithX:x y:y zoom:zoom image:tileImage];
+ }];
+}
+
+@end
+
+@interface FLTTileOverlaysController ()
+
+@property(strong, nonatomic) NSMutableDictionary* tileOverlayIdToController;
+@property(weak, nonatomic) FlutterMethodChannel* methodChannel;
+@property(weak, nonatomic) NSObject<FlutterPluginRegistrar>* registrar;
+@property(weak, nonatomic) GMSMapView* mapView;
+
+@end
+
+@implementation FLTTileOverlaysController
+
+- (instancetype)init:(FlutterMethodChannel*)methodChannel
+ mapView:(GMSMapView*)mapView
+ registrar:(NSObject<FlutterPluginRegistrar>*)registrar {
+ self = [super init];
+ if (self) {
+ self.methodChannel = methodChannel;
+ self.mapView = mapView;
+ self.tileOverlayIdToController = [[NSMutableDictionary alloc] init];
+ self.registrar = registrar;
+ }
+ return self;
+}
+
+- (void)addTileOverlays:(NSArray*)tileOverlaysToAdd {
+ for (NSDictionary* tileOverlay in tileOverlaysToAdd) {
+ NSString* tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay];
+ FLTTileProviderController* tileProvider =
+ [[FLTTileProviderController alloc] init:self.methodChannel tileOverlayId:tileOverlayId];
+ FLTGoogleMapTileOverlayController* controller =
+ [[FLTGoogleMapTileOverlayController alloc] initWithTileLayer:tileProvider
+ mapView:self.mapView];
+ InterpretTileOverlayOptions(tileOverlay, controller, self.registrar);
+ self.tileOverlayIdToController[tileOverlayId] = controller;
+ }
+}
+
+- (void)changeTileOverlays:(NSArray*)tileOverlaysToChange {
+ for (NSDictionary* tileOverlay in tileOverlaysToChange) {
+ NSString* tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay];
+ FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId];
+ if (!controller) {
+ continue;
+ }
+ InterpretTileOverlayOptions(tileOverlay, controller, self.registrar);
+ }
+}
+- (void)removeTileOverlayIds:(NSArray*)tileOverlayIdsToRemove {
+ for (NSString* tileOverlayId in tileOverlayIdsToRemove) {
+ FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId];
+ if (!controller) {
+ continue;
+ }
+ [controller removeTileOverlay];
+ [self.tileOverlayIdToController removeObjectForKey:tileOverlayId];
+ }
+}
+
+- (void)clearTileCache:(NSString*)tileOverlayId {
+ FLTGoogleMapTileOverlayController* controller = self.tileOverlayIdToController[tileOverlayId];
+ if (!controller) {
+ return;
+ }
+ [controller clearTileCache];
+}
+
+- (nullable NSDictionary*)getTileOverlayInfo:(NSString*)tileverlayId {
+ if (self.tileOverlayIdToController[tileverlayId] == nil) {
+ return nil;
+ }
+ return [self.tileOverlayIdToController[tileverlayId] getTileOverlayInfo];
+}
+
++ (NSString*)getTileOverlayId:(NSDictionary*)tileOverlay {
+ return tileOverlay[@"tileOverlayId"];
+}
+
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
index 321ddd3..749ce9e 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#import "GoogleMapController.h"
+#import "FLTGoogleMapTileOverlayController.h"
#import "JsonConversions.h"
#pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations.
@@ -55,6 +56,7 @@
FLTPolygonsController* _polygonsController;
FLTPolylinesController* _polylinesController;
FLTCirclesController* _circlesController;
+ FLTTileOverlaysController* _tileOverlaysController;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -94,6 +96,9 @@
_circlesController = [[FLTCirclesController alloc] init:_channel
mapView:_mapView
registrar:registrar];
+ _tileOverlaysController = [[FLTTileOverlaysController alloc] init:_channel
+ mapView:_mapView
+ registrar:registrar];
id markersToAdd = args[@"markersToAdd"];
if ([markersToAdd isKindOfClass:[NSArray class]]) {
[_markersController addMarkers:markersToAdd];
@@ -110,6 +115,10 @@
if ([circlesToAdd isKindOfClass:[NSArray class]]) {
[_circlesController addCircles:circlesToAdd];
}
+ id tileOverlaysToAdd = args[@"tileOverlaysToAdd"];
+ if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) {
+ [_tileOverlaysController addTileOverlays:tileOverlaysToAdd];
+ }
}
return self;
}
@@ -298,6 +307,24 @@
[_circlesController removeCircleIds:circleIdsToRemove];
}
result(nil);
+ } else if ([call.method isEqualToString:@"tileOverlays#update"]) {
+ id tileOverlaysToAdd = call.arguments[@"tileOverlaysToAdd"];
+ if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) {
+ [_tileOverlaysController addTileOverlays:tileOverlaysToAdd];
+ }
+ id tileOverlaysToChange = call.arguments[@"tileOverlaysToChange"];
+ if ([tileOverlaysToChange isKindOfClass:[NSArray class]]) {
+ [_tileOverlaysController changeTileOverlays:tileOverlaysToChange];
+ }
+ id tileOverlayIdsToRemove = call.arguments[@"tileOverlayIdsToRemove"];
+ if ([tileOverlayIdsToRemove isKindOfClass:[NSArray class]]) {
+ [_tileOverlaysController removeTileOverlayIds:tileOverlayIdsToRemove];
+ }
+ result(nil);
+ } else if ([call.method isEqualToString:@"tileOverlays#clearTileCache"]) {
+ id rawTileOverlayId = call.arguments[@"tileOverlayId"];
+ [_tileOverlaysController clearTileCache:rawTileOverlayId];
+ result(nil);
} else if ([call.method isEqualToString:@"map#isCompassEnabled"]) {
NSNumber* isCompassEnabled = @(_mapView.settings.compassButton);
result(isCompassEnabled);
@@ -341,6 +368,9 @@
} else {
result(@[ @(NO), error ]);
}
+ } else if ([call.method isEqualToString:@"map#getTileOverlayInfo"]) {
+ NSString* rawTileOverlayId = call.arguments[@"tileOverlayId"];
+ result([_tileOverlaysController getTileOverlayInfo:rawTileOverlayId]);
} else {
result(FlutterMethodNotImplemented);
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
index 682c901..703ba63 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
@@ -42,7 +42,11 @@
PolygonId,
Polyline,
PolylineId,
- ScreenCoordinate;
+ ScreenCoordinate,
+ Tile,
+ TileOverlayId,
+ TileOverlay,
+ TileProvider;
part 'src/controller.dart';
part 'src/google_map.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart
index f47b8e5..3967179 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart
@@ -152,6 +152,30 @@
mapId: mapId);
}
+ /// Updates tile overlays configuration.
+ ///
+ /// Change listeners are notified once the update has been made on the
+ /// platform side.
+ ///
+ /// The returned [Future] completes after listeners have been notified.
+ Future<void> _updateTileOverlays(Set<TileOverlay> newTileOverlays) {
+ return _googleMapsFlutterPlatform.updateTileOverlays(
+ newTileOverlays: newTileOverlays, mapId: mapId);
+ }
+
+ /// Clears the tile cache so that all tiles will be requested again from the
+ /// [TileProvider].
+ ///
+ /// The current tiles from this tile overlay will also be
+ /// cleared from the map after calling this method. The API maintains a small
+ /// in-memory cache of tiles. If you want to cache tiles for longer, you
+ /// should implement an on-disk cache.
+ Future<void> clearTileCache(TileOverlayId tileOverlayId) async {
+ assert(tileOverlayId != null);
+ return _googleMapsFlutterPlatform.clearTileCache(tileOverlayId,
+ mapId: mapId);
+ }
+
/// Starts an animated change of the map camera position.
///
/// The returned [Future] completes after the change has been started on the
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
index d7f0f1a..e7f5e32 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
@@ -50,6 +50,7 @@
this.polylines,
this.circles,
this.onCameraMoveStarted,
+ this.tileOverlays,
this.onCameraMove,
this.onCameraIdle,
this.onTap,
@@ -120,6 +121,9 @@
/// Circles to be placed on the map.
final Set<Circle> circles;
+ /// Tile overlays to be placed on the map.
+ final Set<TileOverlay> tileOverlays;
+
/// Called when the camera starts moving.
///
/// This can be initiated by the following:
@@ -232,6 +236,7 @@
'polylinesToAdd': serializePolylineSet(widget.polylines),
'circlesToAdd': serializeCircleSet(widget.circles),
'_webOnlyMapCreationId': _webOnlyMapCreationId,
+ 'tileOverlaysToAdd': serializeTileOverlaySet(widget.tileOverlays),
};
return _googleMapsFlutterPlatform.buildView(
@@ -266,6 +271,7 @@
_updatePolygons();
_updatePolylines();
_updateCircles();
+ _updateTileOverlays();
}
void _updateOptions() async {
@@ -313,6 +319,12 @@
_circles = keyByCircleId(widget.circles);
}
+ void _updateTileOverlays() async {
+ final GoogleMapController controller = await _controller.future;
+ // ignore: unawaited_futures
+ controller._updateTileOverlays(widget.tileOverlays);
+ }
+
Future<void> onPlatformViewCreated(int id) async {
final GoogleMapController controller = await GoogleMapController.init(
id,
@@ -320,6 +332,7 @@
this,
);
_controller.complete(controller);
+ _updateTileOverlays();
if (widget.onMapCreated != null) {
widget.onMapCreated(controller);
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index ef3a06f..be5c0d4 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -7,7 +7,7 @@
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^1.0.0
- google_maps_flutter_platform_interface: ^1.1.0
+ google_maps_flutter_platform_interface: ^1.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
index 9a849bd..d72ac2e 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
@@ -19,6 +19,7 @@
updatePolygons(params);
updatePolylines(params);
updateCircles(params);
+ updateTileOverlays(Map.castFrom<dynamic, dynamic, String, dynamic>(params));
}
MethodChannel channel;
@@ -83,6 +84,12 @@
Set<Circle> circlesToChange;
+ Set<TileOverlayId> tileOverlayIdsToRemove;
+
+ Set<TileOverlay> tileOverlaysToAdd;
+
+ Set<TileOverlay> tileOverlaysToChange;
+
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
case 'map#update':
@@ -97,6 +104,10 @@
case 'polylines#update':
updatePolylines(call.arguments);
return Future<void>.sync(() {});
+ case 'tileOverlays#update':
+ updateTileOverlays(
+ Map.castFrom<dynamic, dynamic, String, dynamic>(call.arguments));
+ return Future<void>.sync(() {});
case 'circles#update':
updateCircles(call.arguments);
return Future<void>.sync(() {});
@@ -292,6 +303,31 @@
circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']);
}
+ void updateTileOverlays(Map<String, dynamic> updateTileOverlayUpdates) {
+ if (updateTileOverlayUpdates == null) {
+ return;
+ }
+ final List<Map<dynamic, dynamic>> tileOverlaysToAddList =
+ updateTileOverlayUpdates['tileOverlaysToAdd'] != null
+ ? List.castFrom<dynamic, Map<dynamic, dynamic>>(
+ updateTileOverlayUpdates['tileOverlaysToAdd'])
+ : null;
+ final List<String> tileOverlayIdsToRemoveList =
+ updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null
+ ? List.castFrom<dynamic, String>(
+ updateTileOverlayUpdates['tileOverlayIdsToRemove'])
+ : null;
+ final List<Map<dynamic, dynamic>> tileOverlaysToChangeList =
+ updateTileOverlayUpdates['tileOverlaysToChange'] != null
+ ? List.castFrom<dynamic, Map<dynamic, dynamic>>(
+ updateTileOverlayUpdates['tileOverlaysToChange'])
+ : null;
+ tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList);
+ tileOverlayIdsToRemove =
+ _deserializeTileOverlayIds(tileOverlayIdsToRemoveList);
+ tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList);
+ }
+
Set<CircleId> _deserializeCircleIds(List<dynamic> circleIds) {
if (circleIds == null) {
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
@@ -329,6 +365,49 @@
return result;
}
+ Set<TileOverlayId> _deserializeTileOverlayIds(List<String> tileOverlayIds) {
+ if (tileOverlayIds == null || tileOverlayIds.isEmpty) {
+ // TODO(iskakaushik): Remove this when collection literals makes it to stable.
+ // https://github.com/flutter/flutter/issues/28312
+ // ignore: prefer_collection_literals
+ return Set<TileOverlayId>();
+ }
+ return tileOverlayIds
+ .map((String tileOverlayId) => TileOverlayId(tileOverlayId))
+ .toSet();
+ }
+
+ Set<TileOverlay> _deserializeTileOverlays(
+ List<Map<dynamic, dynamic>> tileOverlays) {
+ if (tileOverlays == null || tileOverlays.isEmpty) {
+ // TODO(iskakaushik): Remove this when collection literals makes it to stable.
+ // https://github.com/flutter/flutter/issues/28312
+ // ignore: prefer_collection_literals
+ return Set<TileOverlay>();
+ }
+ // TODO(iskakaushik): Remove this when collection literals makes it to stable.
+ // https://github.com/flutter/flutter/issues/28312
+ // ignore: prefer_collection_literals
+ final Set<TileOverlay> result = Set<TileOverlay>();
+ for (Map<dynamic, dynamic> tileOverlayData in tileOverlays) {
+ final String tileOverlayId = tileOverlayData['tileOverlayId'];
+ final bool fadeIn = tileOverlayData['fadeIn'];
+ final double transparency = tileOverlayData['transparency'];
+ final int zIndex = tileOverlayData['zIndex'];
+ final bool visible = tileOverlayData['visible'];
+
+ result.add(TileOverlay(
+ tileOverlayId: TileOverlayId(tileOverlayId),
+ fadeIn: fadeIn,
+ transparency: transparency,
+ zIndex: zIndex,
+ visible: visible,
+ ));
+ }
+
+ return result;
+ }
+
void updateOptions(Map<dynamic, dynamic> options) {
if (options.containsKey('compassEnabled')) {
compassEnabled = options['compassEnabled'];
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart
new file mode 100644
index 0000000..b94d490
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart
@@ -0,0 +1,210 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter/google_maps_flutter.dart';
+
+import 'fake_maps_controllers.dart';
+
+Set<TileOverlay> _toSet({TileOverlay t1, TileOverlay t2, TileOverlay t3}) {
+ final Set<TileOverlay> res = Set<TileOverlay>.identity();
+ if (t1 != null) {
+ res.add(t1);
+ }
+ if (t2 != null) {
+ res.add(t2);
+ }
+ if (t3 != null) {
+ res.add(t3);
+ }
+ return res;
+}
+
+Widget _mapWithTileOverlays(Set<TileOverlay> tileOverlays) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: GoogleMap(
+ initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)),
+ tileOverlays: tileOverlays,
+ ),
+ );
+}
+
+void main() {
+ final FakePlatformViewsController fakePlatformViewsController =
+ FakePlatformViewsController();
+
+ setUpAll(() {
+ SystemChannels.platform_views.setMockMethodCallHandler(
+ fakePlatformViewsController.fakePlatformViewsMethodHandler);
+ });
+
+ setUp(() {
+ fakePlatformViewsController.reset();
+ });
+
+ testWidgets('Initializing a tile overlay', (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1)));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+ expect(platformGoogleMap.tileOverlaysToAdd.length, 1);
+
+ final TileOverlay initializedTileOverlay =
+ platformGoogleMap.tileOverlaysToAdd.first;
+ expect(initializedTileOverlay, equals(t1));
+ expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true);
+ expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true);
+ });
+
+ testWidgets("Adding a tile overlay", (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ final TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"));
+
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1)));
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1, t2: t2)));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+ expect(platformGoogleMap.tileOverlaysToAdd.length, 1);
+
+ final TileOverlay addedTileOverlay =
+ platformGoogleMap.tileOverlaysToAdd.first;
+ expect(addedTileOverlay, equals(t2));
+ expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true);
+
+ expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true);
+ });
+
+ testWidgets("Removing a tile overlay", (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1)));
+ await tester.pumpWidget(_mapWithTileOverlays(null));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+ expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1);
+ expect(platformGoogleMap.tileOverlayIdsToRemove.first,
+ equals(t1.tileOverlayId));
+
+ expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true);
+ expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true);
+ });
+
+ testWidgets("Updating a tile overlay", (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ final TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"), zIndex: 10);
+
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1)));
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t2)));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+ expect(platformGoogleMap.tileOverlaysToChange.length, 1);
+ expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2));
+
+ expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true);
+ expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true);
+ });
+
+ testWidgets("Updating a tile overlay", (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ final TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"), zIndex: 10);
+
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t1)));
+ await tester.pumpWidget(_mapWithTileOverlays(_toSet(t1: t2)));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+ expect(platformGoogleMap.tileOverlaysToChange.length, 1);
+
+ final TileOverlay update = platformGoogleMap.tileOverlaysToChange.first;
+ expect(update, equals(t2));
+ expect(update.zIndex, 10);
+ });
+
+ testWidgets("Multi Update", (WidgetTester tester) async {
+ TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"));
+ final Set<TileOverlay> prev = _toSet(t1: t1, t2: t2);
+ t1 = TileOverlay(
+ tileOverlayId: TileOverlayId("tile_overlay_1"), visible: false);
+ t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"), zIndex: 10);
+ final Set<TileOverlay> cur = _toSet(t1: t1, t2: t2);
+
+ await tester.pumpWidget(_mapWithTileOverlays(prev));
+ await tester.pumpWidget(_mapWithTileOverlays(cur));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+
+ expect(platformGoogleMap.tileOverlaysToChange, cur);
+ expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true);
+ expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true);
+ });
+
+ testWidgets("Multi Update", (WidgetTester tester) async {
+ TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"));
+ final TileOverlay t3 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3"));
+ final Set<TileOverlay> prev = _toSet(t2: t2, t3: t3);
+
+ // t1 is added, t2 is updated, t3 is removed.
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"), zIndex: 10);
+ final Set<TileOverlay> cur = _toSet(t1: t1, t2: t2);
+
+ await tester.pumpWidget(_mapWithTileOverlays(prev));
+ await tester.pumpWidget(_mapWithTileOverlays(cur));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+
+ expect(platformGoogleMap.tileOverlaysToChange.length, 1);
+ expect(platformGoogleMap.tileOverlaysToAdd.length, 1);
+ expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1);
+
+ expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2));
+ expect(platformGoogleMap.tileOverlaysToAdd.first, equals(t1));
+ expect(platformGoogleMap.tileOverlayIdsToRemove.first,
+ equals(t3.tileOverlayId));
+ });
+
+ testWidgets("Partial Update", (WidgetTester tester) async {
+ final TileOverlay t1 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_1"));
+ final TileOverlay t2 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_2"));
+ TileOverlay t3 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3"));
+ final Set<TileOverlay> prev = _toSet(t1: t1, t2: t2, t3: t3);
+ t3 =
+ TileOverlay(tileOverlayId: TileOverlayId("tile_overlay_3"), zIndex: 10);
+ final Set<TileOverlay> cur = _toSet(t1: t1, t2: t2, t3: t3);
+
+ await tester.pumpWidget(_mapWithTileOverlays(prev));
+ await tester.pumpWidget(_mapWithTileOverlays(cur));
+
+ final FakePlatformGoogleMap platformGoogleMap =
+ fakePlatformViewsController.lastCreatedView;
+
+ expect(platformGoogleMap.tileOverlaysToChange, _toSet(t3: t3));
+ expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true);
+ expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true);
+ });
+}