[google_maps_flutter_web] Initial support for custom overlays (#3538)
This is a resubmission of https://github.com/flutter/plugins/pull/6982 from the now archived flutter plugins repo. I'm submitting the changes from the original author, @AsturaPhoenix. The original description is below.
--------
Saves tile bytes to blobs and uses img elements to decode and render. Does not implement opacity, perform caching, or serve placeholder images.
**Issue:** Fixes https://github.com/flutter/flutter/issues/98596
**Known issues:**
- https://github.com/flutter/flutter/issues/116132
- https://github.com/AsturaPhoenix/trip_planner_aquamarine/issues/22
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
index 0427818..6a9b6ad 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.3
+
+* Initial support for custom overlays. [#98596](https://github.com/flutter/flutter/issues/98596).
+
## 0.5.2
* Adds options for gesture handling and tilt controls.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
index 00a448a..fda38f2 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart
@@ -14,14 +14,14 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
-import 'google_maps_controller_test.mocks.dart';
-
-@GenerateMocks(<Type>[], customMocks: <MockSpec<dynamic>>[
- MockSpec<CirclesController>(onMissingStub: OnMissingStub.returnDefault),
- MockSpec<PolygonsController>(onMissingStub: OnMissingStub.returnDefault),
- MockSpec<PolylinesController>(onMissingStub: OnMissingStub.returnDefault),
- MockSpec<MarkersController>(onMissingStub: OnMissingStub.returnDefault),
+@GenerateNiceMocks(<MockSpec<dynamic>>[
+ MockSpec<CirclesController>(),
+ MockSpec<PolygonsController>(),
+ MockSpec<PolylinesController>(),
+ MockSpec<MarkersController>(),
+ MockSpec<TileOverlaysController>(),
])
+import 'google_maps_controller_test.mocks.dart';
/// Test Google Map Controller
void main() {
@@ -194,6 +194,15 @@
}, throwsAssertionError);
});
+ testWidgets('cannot updateTileOverlays after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updateTileOverlays(const <TileOverlay>{});
+ }, throwsAssertionError);
+ });
+
testWidgets('isInfoWindowShown defaults to false',
(WidgetTester tester) async {
controller.dispose();
@@ -208,6 +217,7 @@
late MockMarkersController markers;
late MockPolygonsController polygons;
late MockPolylinesController polylines;
+ late MockTileOverlaysController tileOverlays;
late gmaps.GMap map;
setUp(() {
@@ -215,20 +225,20 @@
markers = MockMarkersController();
polygons = MockPolygonsController();
polylines = MockPolylinesController();
+ tileOverlays = MockTileOverlaysController();
map = gmaps.GMap(html.DivElement());
});
testWidgets('listens to map events', (WidgetTester tester) async {
- controller = createController();
- controller.debugSetOverrides(
- createMap: (_, __) => map,
- circles: circles,
- markers: markers,
- polygons: polygons,
- polylines: polylines,
- );
-
- controller.init();
+ controller = createController()
+ ..debugSetOverrides(
+ createMap: (_, __) => map,
+ circles: circles,
+ markers: markers,
+ polygons: polygons,
+ polylines: polylines,
+ )
+ ..init();
// Trigger events on the map, and verify they've been broadcast to the stream
final Stream<MapEvent<Object?>> capturedEvents = stream.stream.take(5);
@@ -258,26 +268,26 @@
testWidgets("binds geometry controllers to map's",
(WidgetTester tester) async {
- controller = createController();
- controller.debugSetOverrides(
- createMap: (_, __) => map,
- circles: circles,
- markers: markers,
- polygons: polygons,
- polylines: polylines,
- );
-
- controller.init();
+ controller = createController()
+ ..debugSetOverrides(
+ createMap: (_, __) => map,
+ circles: circles,
+ markers: markers,
+ polygons: polygons,
+ polylines: polylines,
+ tileOverlays: tileOverlays,
+ )
+ ..init();
verify(circles.bindToMap(mapId, map));
verify(markers.bindToMap(mapId, map));
verify(polygons.bindToMap(mapId, map));
verify(polylines.bindToMap(mapId, map));
+ verify(tileOverlays.bindToMap(mapId, map));
});
testWidgets('renders initial geometry', (WidgetTester tester) async {
- controller = createController(
- mapObjects: MapObjects(circles: <Circle>{
+ final MapObjects mapObjects = MapObjects(circles: <Circle>{
const Circle(
circleId: CircleId('circle-1'),
zIndex: 1234,
@@ -320,57 +330,25 @@
LatLng(43.354469, -5.851318),
LatLng(43.354762, -5.850824),
])
- }));
+ }, tileOverlays: <TileOverlay>{
+ const TileOverlay(tileOverlayId: TileOverlayId('overlay-1'))
+ });
- controller.debugSetOverrides(
- circles: circles,
- markers: markers,
- polygons: polygons,
- polylines: polylines,
- );
+ controller = createController(mapObjects: mapObjects)
+ ..debugSetOverrides(
+ circles: circles,
+ markers: markers,
+ polygons: polygons,
+ polylines: polylines,
+ tileOverlays: tileOverlays,
+ )
+ ..init();
- controller.init();
-
- final Set<Circle> capturedCircles =
- verify(circles.addCircles(captureAny)).captured[0] as Set<Circle>;
- final Set<Marker> capturedMarkers =
- verify(markers.addMarkers(captureAny)).captured[0] as Set<Marker>;
- final Set<Polygon> capturedPolygons =
- verify(polygons.addPolygons(captureAny)).captured[0]
- as Set<Polygon>;
- final Set<Polyline> capturedPolylines =
- verify(polylines.addPolylines(captureAny)).captured[0]
- as Set<Polyline>;
-
- expect(capturedCircles.first.circleId.value, 'circle-1');
- expect(capturedCircles.first.zIndex, 1234);
- expect(capturedMarkers.first.markerId.value, 'marker-1');
- expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test');
- expect(capturedMarkers.first.infoWindow.title, 'title for test');
- expect(capturedPolygons.first.polygonId.value, 'polygon-1');
- expect(capturedPolygons.elementAt(1).polygonId.value,
- 'polygon-2-with-holes');
- expect(capturedPolygons.elementAt(1).holes, isNot(null));
- expect(capturedPolylines.first.polylineId.value, 'polyline-1');
- });
-
- testWidgets('empty infoWindow does not create InfoWindow instance.',
- (WidgetTester tester) async {
- controller = createController(
- mapObjects: MapObjects(markers: <Marker>{
- const Marker(markerId: MarkerId('marker-1')),
- }));
-
- controller.debugSetOverrides(
- markers: markers,
- );
-
- controller.init();
-
- final Set<Marker> capturedMarkers =
- verify(markers.addMarkers(captureAny)).captured[0] as Set<Marker>;
-
- expect(capturedMarkers.first.infoWindow, InfoWindow.noText);
+ verify(circles.addCircles(mapObjects.circles));
+ verify(markers.addMarkers(mapObjects.markers));
+ verify(polygons.addPolygons(mapObjects.polygons));
+ verify(polylines.addPolylines(mapObjects.polylines));
+ verify(tileOverlays.addTileOverlays(mapObjects.tileOverlays));
});
group('Initialization options', () {
@@ -449,15 +427,12 @@
target: LatLng(43.308, -5.6910),
zoom: 12,
),
- );
-
- controller.debugSetOverrides(
- createMap: (_, gmaps.MapOptions options) {
- capturedOptions = options;
- return map;
- });
-
- controller.init();
+ )
+ ..debugSetOverrides(createMap: (_, gmaps.MapOptions options) {
+ capturedOptions = options;
+ return map;
+ })
+ ..init();
expect(capturedOptions, isNotNull);
expect(capturedOptions!.zoom, 12);
@@ -467,8 +442,7 @@
group('Traffic Layer', () {
testWidgets('by default is disabled', (WidgetTester tester) async {
- controller = createController();
- controller.init();
+ controller = createController()..init();
expect(controller.trafficLayer, isNull);
});
@@ -477,9 +451,9 @@
controller = createController(
mapConfiguration: const MapConfiguration(
trafficEnabled: true,
- ));
- controller.debugSetOverrides(createMap: (_, __) => map);
- controller.init();
+ ))
+ ..debugSetOverrides(createMap: (_, __) => map)
+ ..init();
expect(controller.trafficLayer, isNotNull);
});
});
@@ -496,9 +470,9 @@
..zoom = 10
..center = gmaps.LatLng(0, 0),
);
- controller = createController();
- controller.debugSetOverrides(createMap: (_, __) => map);
- controller.init();
+ controller = createController()
+ ..debugSetOverrides(createMap: (_, __) => map)
+ ..init();
});
group('updateRawOptions', () {
@@ -556,13 +530,9 @@
// These are the methods that get forwarded to other controllers, so we just verify calls.
group('Pass-through methods', () {
- setUp(() {
- controller = createController();
- });
-
testWidgets('updateCircles', (WidgetTester tester) async {
final MockCirclesController mock = MockCirclesController();
- controller.debugSetOverrides(circles: mock);
+ controller = createController()..debugSetOverrides(circles: mock);
final Set<Circle> previous = <Circle>{
const Circle(circleId: CircleId('to-be-updated')),
@@ -589,7 +559,7 @@
testWidgets('updateMarkers', (WidgetTester tester) async {
final MockMarkersController mock = MockMarkersController();
- controller.debugSetOverrides(markers: mock);
+ controller = createController()..debugSetOverrides(markers: mock);
final Set<Marker> previous = <Marker>{
const Marker(markerId: MarkerId('to-be-updated')),
@@ -616,7 +586,7 @@
testWidgets('updatePolygons', (WidgetTester tester) async {
final MockPolygonsController mock = MockPolygonsController();
- controller.debugSetOverrides(polygons: mock);
+ controller = createController()..debugSetOverrides(polygons: mock);
final Set<Polygon> previous = <Polygon>{
const Polygon(polygonId: PolygonId('to-be-updated')),
@@ -643,7 +613,7 @@
testWidgets('updatePolylines', (WidgetTester tester) async {
final MockPolylinesController mock = MockPolylinesController();
- controller.debugSetOverrides(polylines: mock);
+ controller = createController()..debugSetOverrides(polylines: mock);
final Set<Polyline> previous = <Polyline>{
const Polyline(polylineId: PolylineId('to-be-updated')),
@@ -674,11 +644,38 @@
}));
});
+ testWidgets('updateTileOverlays', (WidgetTester tester) async {
+ final MockTileOverlaysController mock = MockTileOverlaysController();
+ controller = createController(
+ mapObjects: MapObjects(tileOverlays: <TileOverlay>{
+ const TileOverlay(tileOverlayId: TileOverlayId('to-be-updated')),
+ const TileOverlay(tileOverlayId: TileOverlayId('to-be-removed')),
+ }))
+ ..debugSetOverrides(tileOverlays: mock);
+
+ controller.updateTileOverlays(<TileOverlay>{
+ const TileOverlay(
+ tileOverlayId: TileOverlayId('to-be-updated'), visible: false),
+ const TileOverlay(tileOverlayId: TileOverlayId('to-be-added')),
+ });
+
+ verify(mock.removeTileOverlays(<TileOverlayId>{
+ const TileOverlayId('to-be-removed'),
+ }));
+ verify(mock.addTileOverlays(<TileOverlay>{
+ const TileOverlay(tileOverlayId: TileOverlayId('to-be-added')),
+ }));
+ verify(mock.changeTileOverlays(<TileOverlay>{
+ const TileOverlay(
+ tileOverlayId: TileOverlayId('to-be-updated'), visible: false),
+ }));
+ });
+
testWidgets('infoWindow visibility', (WidgetTester tester) async {
final MockMarkersController mock = MockMarkersController();
const MarkerId markerId = MarkerId('marker-with-infowindow');
when(mock.isInfoWindowShown(markerId)).thenReturn(true);
- controller.debugSetOverrides(markers: mock);
+ controller = createController()..debugSetOverrides(markers: mock);
controller.showInfoWindow(markerId);
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
index 4bc5f36..f73b3d7 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
@@ -403,3 +403,65 @@
returnValueForMissingStub: null,
);
}
+
+/// A class which mocks [TileOverlaysController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTileOverlaysController extends _i1.Mock
+ implements _i3.TileOverlaysController {
+ @override
+ _i2.GMap get googleMap => (super.noSuchMethod(
+ Invocation.getter(#googleMap),
+ returnValue: _FakeGMap_0(
+ this,
+ Invocation.getter(#googleMap),
+ ),
+ returnValueForMissingStub: _FakeGMap_0(
+ this,
+ Invocation.getter(#googleMap),
+ ),
+ ) as _i2.GMap);
+ @override
+ set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod(
+ Invocation.setter(
+ #googleMap,
+ _googleMap,
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void addTileOverlays(Set<_i4.TileOverlay>? tileOverlays) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #addTileOverlays,
+ [tileOverlays],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void changeTileOverlays(Set<_i4.TileOverlay>? tileOverlays) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #changeTileOverlays,
+ [tileOverlays],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void removeTileOverlays(Set<_i4.TileOverlayId>? tileOverlayIds) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #removeTileOverlays,
+ [tileOverlayIds],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void clearTileCache(_i4.TileOverlayId? tileOverlayId) => super.noSuchMethod(
+ Invocation.method(
+ #clearTileCache,
+ [tileOverlayId],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
index c773596..36b4d11 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart
@@ -14,12 +14,9 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
+@GenerateNiceMocks(<MockSpec<dynamic>>[MockSpec<GoogleMapController>()])
import 'google_maps_plugin_test.mocks.dart';
-@GenerateMocks(<Type>[], customMocks: <MockSpec<dynamic>>[
- MockSpec<GoogleMapController>(onMissingStub: OnMissingStub.returnDefault),
-])
-
/// Test GoogleMapsPlugin
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -208,28 +205,6 @@
});
});
- group('Noop methods:', () {
- const int mapId = 0;
- setUp(() {
- plugin.debugSetMapById(<int, GoogleMapController>{mapId: controller});
- });
- // Options
- testWidgets('updateTileOverlays', (WidgetTester tester) async {
- final Future<void> update = plugin.updateTileOverlays(
- mapId: mapId,
- newTileOverlays: <TileOverlay>{},
- );
- expect(update, completion(null));
- });
- testWidgets('updateTileOverlays', (WidgetTester tester) async {
- final Future<void> update = plugin.clearTileCache(
- const TileOverlayId('any'),
- mapId: mapId,
- );
- expect(update, completion(null));
- });
- });
-
// These methods only pass-through values from the plugin to the controller
// so we verify them all together here...
group('Pass-through methods:', () {
@@ -287,6 +262,24 @@
verify(controller.updateCircles(expectedUpdates));
});
+ // Tile Overlays
+ testWidgets('updateTileOverlays', (WidgetTester tester) async {
+ final Set<TileOverlay> expectedOverlays = <TileOverlay>{
+ const TileOverlay(tileOverlayId: TileOverlayId('overlay'))
+ };
+
+ await plugin.updateTileOverlays(
+ newTileOverlays: expectedOverlays, mapId: mapId);
+
+ verify(controller.updateTileOverlays(expectedOverlays));
+ });
+ testWidgets('clearTileCache', (WidgetTester tester) async {
+ const TileOverlayId tileOverlayId = TileOverlayId('Dory');
+
+ await plugin.clearTileCache(tileOverlayId, mapId: mapId);
+
+ verify(controller.clearTileCache(tileOverlayId));
+ });
// Camera
testWidgets('animateCamera', (WidgetTester tester) async {
final CameraUpdate expectedUpdates = CameraUpdate.newLatLng(
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
index 36e6052..831bda1 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
@@ -126,6 +126,7 @@
_i4.CirclesController? circles,
_i4.PolygonsController? polygons,
_i4.PolylinesController? polylines,
+ _i4.TileOverlaysController? tileOverlays,
}) =>
super.noSuchMethod(
Invocation.method(
@@ -137,6 +138,7 @@
#circles: circles,
#polygons: polygons,
#polylines: polylines,
+ #tileOverlays: tileOverlays,
},
),
returnValueForMissingStub: null,
@@ -286,6 +288,23 @@
returnValueForMissingStub: null,
);
@override
+ void updateTileOverlays(Set<_i2.TileOverlay>? newOverlays) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #updateTileOverlays,
+ [newOverlays],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ void clearTileCache(_i2.TileOverlayId? id) => super.noSuchMethod(
+ Invocation.method(
+ #clearTileCache,
+ [id],
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
void showInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod(
Invocation.method(
#showInfoWindow,
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart
new file mode 100644
index 0000000..29f902f
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlay_test.dart
@@ -0,0 +1,119 @@
+// Copyright 2013 The Flutter 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 'dart:convert';
+import 'dart:html' as html;
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'resources/tile16_base64.dart';
+
+class NoTileProvider implements TileProvider {
+ const NoTileProvider();
+
+ @override
+ Future<Tile> getTile(int x, int y, int? zoom) async => TileProvider.noTile;
+}
+
+class TestTileProvider implements TileProvider {
+ const TestTileProvider();
+
+ @override
+ Future<Tile> getTile(int x, int y, int? zoom) async =>
+ Tile(16, 16, const Base64Decoder().convert(tile16Base64));
+}
+
+/// Test Overlays
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('TileOverlayController', () {
+ const TileOverlayId id = TileOverlayId('');
+
+ testWidgets('minimal initialization', (WidgetTester tester) async {
+ final TileOverlayController controller = TileOverlayController(
+ tileOverlay: const TileOverlay(tileOverlayId: id),
+ );
+
+ final gmaps.Size size = controller.gmMapType.tileSize!;
+ expect(size.width, TileOverlayController.logicalTileSize);
+ expect(size.height, TileOverlayController.logicalTileSize);
+ expect(controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document),
+ null);
+ });
+
+ testWidgets('produces image tiles', (WidgetTester tester) async {
+ final TileOverlayController controller = TileOverlayController(
+ tileOverlay: const TileOverlay(
+ tileOverlayId: id,
+ tileProvider: TestTileProvider(),
+ ),
+ );
+
+ final html.ImageElement img =
+ controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
+ as html.ImageElement;
+ expect(img.naturalWidth, 0);
+ expect(img.naturalHeight, 0);
+ expect(img.hidden, true);
+
+ // Wait until the image is fully loaded and decoded before re-reading its attributes.
+ await img.onLoad.first;
+ await img.decode();
+
+ expect(img.hidden, false);
+ expect(img.naturalWidth, 16);
+ expect(img.naturalHeight, 16);
+ });
+
+ testWidgets('update', (WidgetTester tester) async {
+ final TileOverlayController controller = TileOverlayController(
+ tileOverlay: const TileOverlay(
+ tileOverlayId: id,
+ tileProvider: NoTileProvider(),
+ ),
+ );
+ {
+ final html.ImageElement img =
+ controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
+ as html.ImageElement;
+ await null; // let `getTile` `then` complete
+ expect(
+ img.src,
+ isEmpty,
+ reason: 'The NoTileProvider never updates the img src',
+ );
+ }
+
+ controller.update(const TileOverlay(
+ tileOverlayId: id,
+ tileProvider: TestTileProvider(),
+ ));
+ {
+ final html.ImageElement img =
+ controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document)!
+ as html.ImageElement;
+ await img.onLoad.first;
+ expect(
+ img.src,
+ isNotEmpty,
+ reason: 'The img `src` should eventually become the Blob URL.',
+ );
+ }
+
+ controller.update(const TileOverlay(tileOverlayId: id));
+ {
+ expect(
+ controller.gmMapType.getTile!(gmaps.Point(0, 0), 0, html.document),
+ null,
+ reason: 'Setting a null tileProvider should work.',
+ );
+ }
+ });
+ });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart
new file mode 100644
index 0000000..8b6b346
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart
@@ -0,0 +1,172 @@
+// Copyright 2013 The Flutter 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 'dart:async';
+import 'dart:html' as html;
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'
+ hide GoogleMapController;
+import 'package:integration_test/integration_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+@GenerateNiceMocks(<MockSpec<dynamic>>[MockSpec<TileProvider>()])
+import 'overlays_test.mocks.dart';
+
+MockTileProvider neverTileProvider() {
+ final MockTileProvider tileProvider = MockTileProvider();
+ when(tileProvider.getTile(any, any, any))
+ .thenAnswer((_) => Completer<Tile>().future);
+ return tileProvider;
+}
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('TileOverlaysController', () {
+ late TileOverlaysController controller;
+ late gmaps.GMap map;
+ late List<MockTileProvider> tileProviders;
+ late List<TileOverlay> tileOverlays;
+
+ /// Queries the current overlay map types for tiles at x = 0, y = 0, zoom =
+ /// 0.
+ void probeTiles() {
+ for (final gmaps.MapType? mapType in map.overlayMapTypes!.array!) {
+ mapType?.getTile!(gmaps.Point(0, 0), 0, html.document);
+ }
+ }
+
+ setUp(() {
+ controller = TileOverlaysController();
+ map = gmaps.GMap(html.DivElement());
+ controller.googleMap = map;
+
+ tileProviders = <MockTileProvider>[
+ for (int i = 0; i < 3; ++i) neverTileProvider()
+ ];
+
+ tileOverlays = <TileOverlay>[
+ for (int i = 0; i < 3; ++i)
+ TileOverlay(
+ tileOverlayId: TileOverlayId('$i'),
+ tileProvider: tileProviders[i],
+ zIndex: i)
+ ];
+ });
+
+ testWidgets('addTileOverlays', (WidgetTester tester) async {
+ controller.addTileOverlays(<TileOverlay>{...tileOverlays});
+ probeTiles();
+ verifyInOrder(<dynamic>[
+ tileProviders[0].getTile(any, any, any),
+ tileProviders[1].getTile(any, any, any),
+ tileProviders[2].getTile(any, any, any),
+ ]);
+ verifyNoMoreInteractions(tileProviders[0]);
+ verifyNoMoreInteractions(tileProviders[1]);
+ verifyNoMoreInteractions(tileProviders[2]);
+ });
+
+ testWidgets('changeTileOverlays', (WidgetTester tester) async {
+ controller.addTileOverlays(<TileOverlay>{...tileOverlays});
+
+ // Set overlay 0 visiblity to false; flip z ordering of 1 and 2, leaving 1
+ // unchanged.
+ controller.changeTileOverlays(<TileOverlay>{
+ tileOverlays[0].copyWith(visibleParam: false),
+ tileOverlays[2].copyWith(zIndexParam: 0),
+ });
+
+ probeTiles();
+
+ verifyInOrder(<dynamic>[
+ tileProviders[2].getTile(any, any, any),
+ tileProviders[1].getTile(any, any, any),
+ ]);
+ verifyZeroInteractions(tileProviders[0]);
+ verifyNoMoreInteractions(tileProviders[1]);
+ verifyNoMoreInteractions(tileProviders[2]);
+
+ // Re-enable overlay 0.
+ controller.changeTileOverlays(
+ <TileOverlay>{tileOverlays[0].copyWith(visibleParam: true)});
+
+ probeTiles();
+
+ verify(tileProviders[2].getTile(any, any, any));
+ verifyInOrder(<dynamic>[
+ tileProviders[0].getTile(any, any, any),
+ tileProviders[1].getTile(any, any, any),
+ ]);
+ verifyNoMoreInteractions(tileProviders[0]);
+ verifyNoMoreInteractions(tileProviders[1]);
+ verifyNoMoreInteractions(tileProviders[2]);
+ });
+
+ testWidgets(
+ 'updating the z index of a hidden layer does not make it visible',
+ (WidgetTester tester) async {
+ controller.addTileOverlays(<TileOverlay>{...tileOverlays});
+
+ controller.changeTileOverlays(<TileOverlay>{
+ tileOverlays[0].copyWith(zIndexParam: -1, visibleParam: false),
+ });
+
+ probeTiles();
+ verifyZeroInteractions(tileProviders[0]);
+ });
+
+ testWidgets('removeTileOverlays', (WidgetTester tester) async {
+ controller.addTileOverlays(<TileOverlay>{...tileOverlays});
+
+ controller.removeTileOverlays(<TileOverlayId>{
+ tileOverlays[0].tileOverlayId,
+ tileOverlays[2].tileOverlayId,
+ });
+
+ probeTiles();
+
+ verify(tileProviders[1].getTile(any, any, any));
+ verifyZeroInteractions(tileProviders[0]);
+ verifyZeroInteractions(tileProviders[2]);
+ });
+
+ testWidgets('clearTileCache', (WidgetTester tester) async {
+ final Completer<GoogleMapController> controllerCompleter =
+ Completer<GoogleMapController>();
+ await tester.pumpWidget(MaterialApp(
+ home: Scaffold(
+ body: GoogleMap(
+ initialCameraPosition: const CameraPosition(
+ target: LatLng(43.3078, -5.6958),
+ zoom: 14,
+ ),
+ tileOverlays: <TileOverlay>{...tileOverlays.take(2)},
+ onMapCreated: (GoogleMapController value) {
+ controllerCompleter.complete(value);
+ addTearDown(() => value.dispose());
+ },
+ ))));
+
+ // This is needed to kick-off the rendering of the JS Map flutter widget
+ await tester.pump();
+ final GoogleMapController controller = await controllerCompleter.future;
+
+ await tester.pump();
+ verify(tileProviders[0].getTile(any, any, any));
+ verify(tileProviders[1].getTile(any, any, any));
+
+ await controller.clearTileCache(tileOverlays[0].tileOverlayId);
+
+ await tester.pump();
+ verify(tileProviders[0].getTile(any, any, any));
+ verifyNoMoreInteractions(tileProviders[1]);
+ });
+ });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.mocks.dart
new file mode 100644
index 0000000..126f7c5
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.mocks.dart
@@ -0,0 +1,77 @@
+// Mocks generated by Mockito 5.4.1 from annotations
+// in google_maps_flutter_web_integration_tests/integration_test/overlays_test.dart.
+// Do not manually edit this file.
+
+// @dart=2.19
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+
+import 'package:google_maps_flutter_platform_interface/src/types/types.dart'
+ as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeTile_0 extends _i1.SmartFake implements _i2.Tile {
+ _FakeTile_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [TileProvider].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTileProvider extends _i1.Mock implements _i2.TileProvider {
+ @override
+ _i3.Future<_i2.Tile> getTile(
+ int? x,
+ int? y,
+ int? zoom,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getTile,
+ [
+ x,
+ y,
+ zoom,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Tile>.value(_FakeTile_0(
+ this,
+ Invocation.method(
+ #getTile,
+ [
+ x,
+ y,
+ zoom,
+ ],
+ ),
+ )),
+ returnValueForMissingStub: _i3.Future<_i2.Tile>.value(_FakeTile_0(
+ this,
+ Invocation.method(
+ #getTile,
+ [
+ x,
+ y,
+ zoom,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Tile>);
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/resources/tile16_base64.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/resources/tile16_base64.dart
new file mode 100644
index 0000000..0728b17
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/resources/tile16_base64.dart
@@ -0,0 +1,9 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// 16x16 transparent png.
+const String tile16Base64 =
+ 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAA'
+ 'Cxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAATSURBVDhPYxgFo2AUjAIwYGAAAAQQAAGn'
+ 'RHxjAAAAAElFTkSuQmCC';
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
index df0b8de..65448ab 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart
@@ -9,6 +9,7 @@
import 'dart:html';
import 'dart:js_util';
+import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -31,6 +32,8 @@
part 'src/google_maps_flutter_web.dart';
part 'src/marker.dart';
part 'src/markers.dart';
+part 'src/overlay.dart';
+part 'src/overlays.dart';
part 'src/polygon.dart';
part 'src/polygons.dart';
part 'src/polyline.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
index f49a687..fbb1942 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart
@@ -25,11 +25,13 @@
_polygons = mapObjects.polygons,
_polylines = mapObjects.polylines,
_circles = mapObjects.circles,
+ _tileOverlays = mapObjects.tileOverlays,
_lastMapConfiguration = mapConfiguration {
_circlesController = CirclesController(stream: _streamController);
_polygonsController = PolygonsController(stream: _streamController);
_polylinesController = PolylinesController(stream: _streamController);
_markersController = MarkersController(stream: _streamController);
+ _tileOverlaysController = TileOverlaysController();
// Register the view factory that will hold the `_div` that holds the map in the DOM.
// The `_div` needs to be created outside of the ViewFactory (and cached!) so we can
@@ -53,6 +55,7 @@
final Set<Polygon> _polygons;
final Set<Polyline> _polylines;
final Set<Circle> _circles;
+ Set<TileOverlay> _tileOverlays;
// The configuration passed by the user, before converting to gmaps.
// Caching this allows us to re-create the map faithfully when needed.
MapConfiguration _lastMapConfiguration = const MapConfiguration();
@@ -108,6 +111,7 @@
PolygonsController? _polygonsController;
PolylinesController? _polylinesController;
MarkersController? _markersController;
+ TileOverlaysController? _tileOverlaysController;
// Keeps track if _attachGeometryControllers has been called or not.
bool _controllersBoundToMap = false;
@@ -122,12 +126,14 @@
CirclesController? circles,
PolygonsController? polygons,
PolylinesController? polylines,
+ TileOverlaysController? tileOverlays,
}) {
_overrideCreateMap = createMap;
_markersController = markers ?? _markersController;
_circlesController = circles ?? _circlesController;
_polygonsController = polygons ?? _polygonsController;
_polylinesController = polylines ?? _polylinesController;
+ _tileOverlaysController = tileOverlays ?? _tileOverlaysController;
}
DebugCreateMapFunction? _overrideCreateMap;
@@ -182,13 +188,7 @@
_attachGeometryControllers(map);
// Now attach the geometry, traffic and any other layers...
- _renderInitialGeometry(
- markers: _markers,
- circles: _circles,
- polygons: _polygons,
- polylines: _polylines,
- );
-
+ _renderInitialGeometry();
_setTrafficLayer(map, _lastMapConfiguration.trafficEnabled ?? false);
}
@@ -241,22 +241,20 @@
'Cannot attach a map to a null PolylinesController instance.');
assert(_markersController != null,
'Cannot attach a map to a null MarkersController instance.');
+ assert(_tileOverlaysController != null,
+ 'Cannot attach a map to a null TileOverlaysController instance.');
_circlesController!.bindToMap(_mapId, map);
_polygonsController!.bindToMap(_mapId, map);
_polylinesController!.bindToMap(_mapId, map);
_markersController!.bindToMap(_mapId, map);
+ _tileOverlaysController!.bindToMap(_mapId, map);
_controllersBoundToMap = true;
}
// Renders the initial sets of geometry.
- void _renderInitialGeometry({
- Set<Marker> markers = const <Marker>{},
- Set<Circle> circles = const <Circle>{},
- Set<Polygon> polygons = const <Polygon>{},
- Set<Polyline> polylines = const <Polyline>{},
- }) {
+ void _renderInitialGeometry() {
assert(
_controllersBoundToMap,
'Geometry controllers must be bound to a map before any geometry can '
@@ -266,10 +264,11 @@
// in the [_attachGeometryControllers] method, which ensures that all these
// controllers below are *not* null.
- _markersController!.addMarkers(markers);
- _circlesController!.addCircles(circles);
- _polygonsController!.addPolygons(polygons);
- _polylinesController!.addPolylines(polylines);
+ _markersController!.addMarkers(_markers);
+ _circlesController!.addCircles(_circles);
+ _polygonsController!.addPolygons(_polygons);
+ _polylinesController!.addPolylines(_polylines);
+ _tileOverlaysController!.addTileOverlays(_tileOverlays);
}
// Merges new options coming from the plugin into _lastConfiguration.
@@ -407,6 +406,25 @@
_markersController?.removeMarkers(updates.markerIdsToRemove);
}
+ /// Updates the set of [TileOverlay]s.
+ void updateTileOverlays(Set<TileOverlay> newOverlays) {
+ final MapsObjectUpdates<TileOverlay> updates =
+ MapsObjectUpdates<TileOverlay>.from(_tileOverlays, newOverlays,
+ objectName: 'tileOverlay');
+ assert(_tileOverlaysController != null,
+ 'Cannot update tile overlays after dispose().');
+ _tileOverlaysController?.addTileOverlays(updates.objectsToAdd);
+ _tileOverlaysController?.changeTileOverlays(updates.objectsToChange);
+ _tileOverlaysController
+ ?.removeTileOverlays(updates.objectIdsToRemove.cast<TileOverlayId>());
+ _tileOverlays = newOverlays;
+ }
+
+ /// Clears the tile cache associated with the given [TileOverlayId].
+ void clearTileCache(TileOverlayId id) {
+ _tileOverlaysController?.clearTileCache(id);
+ }
+
/// Shows the [InfoWindow] of the marker identified by its [MarkerId].
void showInfoWindow(MarkerId markerId) {
assert(_markersController != null,
@@ -439,6 +457,7 @@
_polygonsController = null;
_polylinesController = null;
_markersController = null;
+ _tileOverlaysController = null;
_streamController.close();
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
index 049a6a2..6b91e94 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart
@@ -95,7 +95,7 @@
required Set<TileOverlay> newTileOverlays,
required int mapId,
}) async {
- return; // Noop for now!
+ _map(mapId).updateTileOverlays(newTileOverlays);
}
@override
@@ -103,7 +103,7 @@
TileOverlayId tileOverlayId, {
required int mapId,
}) async {
- return; // Noop for now!
+ _map(mapId).clearTileCache(tileOverlayId);
}
/// Applies the given `cameraUpdate` to the current viewport (with animation).
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart
new file mode 100644
index 0000000..86f5387
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlay.dart
@@ -0,0 +1,76 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of google_maps_flutter_web;
+
+/// This wraps a [TileOverlay] in a [gmaps.MapType].
+class TileOverlayController {
+ /// Creates a `TileOverlayController` that wraps a [TileOverlay] object and its corresponding [gmaps.MapType].
+ TileOverlayController({
+ required TileOverlay tileOverlay,
+ }) {
+ update(tileOverlay);
+ }
+
+ /// The size in pixels of the (square) tiles passed to the Maps SDK.
+ ///
+ /// Even though the web supports any size, and rectangular tiles, for
+ /// for consistency with mobile, this is not configurable on the web.
+ /// (Both Android and iOS prefer square 256px tiles @ 1x DPI)
+ ///
+ /// For higher DPI screens, the Tile that is actually returned can be larger
+ /// than 256px square.
+ static const int logicalTileSize = 256;
+
+ /// Updates the [gmaps.MapType] and cached properties with an updated
+ /// [TileOverlay].
+ void update(TileOverlay tileOverlay) {
+ _tileOverlay = tileOverlay;
+ _gmMapType = gmaps.MapType()
+ ..tileSize = gmaps.Size(logicalTileSize, logicalTileSize)
+ ..getTile = _getTile;
+ }
+
+ /// Renders a Tile for gmaps; delegating to the configured [TileProvider].
+ HtmlElement? _getTile(
+ gmaps.Point? tileCoord,
+ num? zoom,
+ Document? ownerDocument,
+ ) {
+ if (_tileOverlay.tileProvider == null) {
+ return null;
+ }
+
+ final ImageElement img =
+ ownerDocument!.createElement('img') as ImageElement;
+ img.width = img.height = logicalTileSize;
+ img.hidden = true;
+ img.setAttribute('decoding', 'async');
+
+ _tileOverlay.tileProvider!
+ .getTile(tileCoord!.x!.toInt(), tileCoord.y!.toInt(), zoom?.toInt())
+ .then((Tile tile) {
+ if (tile.data == null) {
+ return;
+ }
+ // Using img lets us take advantage of native decoding.
+ final String src = Url.createObjectUrl(Blob(<Object?>[tile.data]));
+ img.src = src;
+ img.addEventListener('load', (_) {
+ img.hidden = false;
+ Url.revokeObjectUrl(src);
+ });
+ });
+
+ return img;
+ }
+
+ /// The [gmaps.MapType] produced by this controller.
+ gmaps.MapType get gmMapType => _gmMapType;
+ late gmaps.MapType _gmMapType;
+
+ /// The [TileOverlay] providing data for this controller.
+ TileOverlay get tileOverlay => _tileOverlay;
+ late TileOverlay _tileOverlay;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart
new file mode 100644
index 0000000..aa6c191
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of google_maps_flutter_web;
+
+/// This class manages all the [TileOverlayController]s associated to a [GoogleMapController].
+class TileOverlaysController extends GeometryController {
+ final Map<TileOverlayId, TileOverlayController> _tileOverlays =
+ <TileOverlayId, TileOverlayController>{};
+ final List<TileOverlayController> _visibleTileOverlays =
+ <TileOverlayController>[];
+
+ // Inserts `tileOverlayController` into the list of visible overlays, and the current [googleMap].
+ //
+ // After insertion, the arrays stay sorted by ascending z-index.
+ void _insertZSorted(TileOverlayController tileOverlayController) {
+ final int index = _visibleTileOverlays.lowerBoundBy<num>(
+ tileOverlayController,
+ (TileOverlayController c) => c.tileOverlay.zIndex);
+
+ googleMap.overlayMapTypes!.insertAt(index, tileOverlayController.gmMapType);
+ _visibleTileOverlays.insert(index, tileOverlayController);
+ }
+
+ // Removes `tileOverlayController` from the list of visible overlays.
+ void _remove(TileOverlayController tileOverlayController) {
+ final int index = _visibleTileOverlays.indexOf(tileOverlayController);
+ if (index < 0) {
+ return;
+ }
+
+ googleMap.overlayMapTypes!.removeAt(index);
+ _visibleTileOverlays.removeAt(index);
+ }
+
+ /// Adds new [TileOverlay]s to this controller.
+ ///
+ /// Wraps the [TileOverlay]s in corresponding [TileOverlayController]s.
+ void addTileOverlays(Set<TileOverlay> tileOverlaysToAdd) {
+ tileOverlaysToAdd.forEach(_addTileOverlay);
+ }
+
+ void _addTileOverlay(TileOverlay tileOverlay) {
+ final TileOverlayController controller = TileOverlayController(
+ tileOverlay: tileOverlay,
+ );
+ _tileOverlays[tileOverlay.tileOverlayId] = controller;
+
+ if (tileOverlay.visible) {
+ _insertZSorted(controller);
+ }
+ }
+
+ /// Updates [TileOverlay]s with new options.
+ void changeTileOverlays(Set<TileOverlay> tileOverlays) {
+ tileOverlays.forEach(_changeTileOverlay);
+ }
+
+ void _changeTileOverlay(TileOverlay tileOverlay) {
+ final TileOverlayController controller =
+ _tileOverlays[tileOverlay.tileOverlayId]!;
+
+ final bool wasVisible = controller.tileOverlay.visible;
+ final bool isVisible = tileOverlay.visible;
+
+ controller.update(tileOverlay);
+
+ if (wasVisible) {
+ _remove(controller);
+ }
+ if (isVisible) {
+ _insertZSorted(controller);
+ }
+ }
+
+ /// Removes the tile overlays associated with the given [TileOverlayId]s.
+ void removeTileOverlays(Set<TileOverlayId> tileOverlayIds) {
+ tileOverlayIds.forEach(_removeTileOverlay);
+ }
+
+ void _removeTileOverlay(TileOverlayId tileOverlayId) {
+ final TileOverlayController? controller =
+ _tileOverlays.remove(tileOverlayId);
+ if (controller != null) {
+ _remove(controller);
+ }
+ }
+
+ /// Invalidates the tile overlay associated with the given [TileOverlayId].
+ void clearTileCache(TileOverlayId tileOverlayId) {
+ final TileOverlayController? controller = _tileOverlays[tileOverlayId];
+ if (controller != null && controller.tileOverlay.visible) {
+ final int i = _visibleTileOverlays.indexOf(controller);
+ // This causes the map to reload the overlay.
+ googleMap.overlayMapTypes!.setAt(i, controller.gmMapType);
+ }
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
index f1eb09c..642c2e4 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
@@ -2,7 +2,7 @@
description: Web platform implementation of google_maps_flutter
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 0.5.2
+version: 0.5.3
environment:
sdk: ">=2.18.0 <4.0.0"
@@ -17,6 +17,7 @@
fileName: google_maps_flutter_web.dart
dependencies:
+ collection: ^1.16.0
flutter:
sdk: flutter
flutter_web_plugins: