[google_maps_flutter_web] Migrate to null-safety. (#3726)
* Updates JS-interop package to google_maps ^5.1.0
* Breaking changes:
* The property `icon` of a `Marker` cannot be `null`. Defaults to `BitmapDescriptor.defaultMarker`
* The property `initialCameraPosition` of a `GoogleMapController` can't be `null`. It is also marked as `required`.
* The parameter `creationId` of the `buildView` method cannot be `null` (this should be handled internally for users of the plugin)
* Most of the Controller methods can't be called after `remove`/`dispose`. Calling these methods now will throw an Assertion error. Before it'd be a no-op, or a null-pointer exception.
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 29e9287..fd56b64 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,12 @@
+## 0.3.0
+
+* Migrate package to null-safety.
+* **Breaking changes:**
+ * The property `icon` of a `Marker` cannot be `null`. Defaults to `BitmapDescriptor.defaultMarker`
+ * The property `initialCameraPosition` of a `GoogleMapController` can't be `null`. It is also marked as `required`.
+ * The parameter `creationId` of the `buildView` method cannot be `null` (this should be handled internally for users of the plugin)
+ * Most of the Controller methods can't be called after `remove`/`dispose`. Calling these methods now will throw an Assertion error. Before it'd be a no-op, or a null-pointer exception.
+
## 0.2.1
* Move integration tests to `example`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/README.md b/packages/google_maps_flutter/google_maps_flutter_web/example/README.md
index 0ec01e0..582288a 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/README.md
@@ -19,3 +19,13 @@
* Single: `./run_test.sh integration_test/TEST_NAME.dart`
* All: `./run_test.sh`
+
+## Mocks
+
+There's new `.mocks.dart` files next to the test files that use them.
+
+Mock files are [generated by `package:mockito`](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#code-generation). The contents of these files can change with how the mocks are used within the tests, in addition to actual changes in the APIs they're mocking.
+
+Mock files can be updated either manually by running the following command: `flutter pub run build_runner build` (or the `regen_mocks.sh` script), or automatically on each call to the `run_test.sh` script.
+
+Please, add whatever changes show up in mock files to your PRs, or CI will fail.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/build.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/build.yaml
new file mode 100644
index 0000000..db3104b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/build.yaml
@@ -0,0 +1,6 @@
+targets:
+ $default:
+ sources:
+ - integration_test/*.dart
+ - lib/$lib$
+ - $package$
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 fd4df2e..1d33eea 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
@@ -2,43 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
+import 'dart:html' as html;
-import 'package:integration_test/integration_test.dart';
-import 'package:google_maps/google_maps.dart' as gmaps;
-import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
+import 'package:flutter/widgets.dart';
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 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
-import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'google_maps_controller_test.mocks.dart';
-class _MockCirclesController extends Mock implements CirclesController {}
+// This value is used when comparing long~num, like
+// LatLng values.
+const _acceptableDelta = 0.0000000001;
-class _MockPolygonsController extends Mock implements PolygonsController {}
-
-class _MockPolylinesController extends Mock implements PolylinesController {}
-
-class _MockMarkersController extends Mock implements MarkersController {}
-
-class _MockGMap extends Mock implements gmaps.GMap {
- final onClickController = StreamController<gmaps.MouseEvent>.broadcast();
- @override
- Stream<gmaps.MouseEvent> get onClick => onClickController.stream;
-
- final onRightclickController = StreamController<gmaps.MouseEvent>.broadcast();
- @override
- Stream<gmaps.MouseEvent> get onRightclick => onRightclickController.stream;
-
- final onBoundsChangedController = StreamController<dynamic>.broadcast();
- @override
- Stream<dynamic> get onBoundsChanged => onBoundsChangedController.stream;
-
- final onIdleController = StreamController<dynamic>.broadcast();
- @override
- Stream<dynamic> get onIdle => onIdleController.stream;
-}
+@GenerateMocks([], customMocks: [
+ MockSpec<CirclesController>(returnNullOnMissingStub: true),
+ MockSpec<PolygonsController>(returnNullOnMissingStub: true),
+ MockSpec<PolylinesController>(returnNullOnMissingStub: true),
+ MockSpec<MarkersController>(returnNullOnMissingStub: true),
+])
/// Test Google Map Controller
void main() {
@@ -46,8 +33,8 @@
group('GoogleMapController', () {
final int mapId = 33930;
- GoogleMapController controller;
- StreamController<MapEvent> stream;
+ late GoogleMapController controller;
+ late StreamController<MapEvent> stream;
// Creates a controller with the default mapId and stream controller, and any `options` needed.
GoogleMapController _createController({
@@ -57,17 +44,18 @@
Set<Polygon> polygons = const <Polygon>{},
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
- Map<String, dynamic> options,
+ Map<String, dynamic> options = const <String, dynamic>{},
}) {
return GoogleMapController(
- mapId: mapId,
- streamController: stream,
- initialCameraPosition: initialCameraPosition,
- markers: markers,
- polygons: polygons,
- polylines: polylines,
- circles: circles,
- mapOptions: options ?? <String, dynamic>{});
+ mapId: mapId,
+ streamController: stream,
+ initialCameraPosition: initialCameraPosition,
+ markers: markers,
+ polygons: polygons,
+ polylines: polylines,
+ circles: circles,
+ mapOptions: options,
+ );
}
setUp(() {
@@ -81,7 +69,9 @@
testWidgets('constructor creates widget', (WidgetTester tester) async {
expect(controller.widget, isNotNull);
- expect(controller.widget.viewType, endsWith('$mapId'));
+ expect(controller.widget, isA<HtmlElementView>());
+ expect((controller.widget as HtmlElementView).viewType,
+ endsWith('$mapId'));
});
testWidgets('widget is cached when reused', (WidgetTester tester) async {
@@ -90,27 +80,130 @@
expect(identical(first, again), isTrue);
});
- testWidgets('dispose closes the stream and removes the widget',
- (WidgetTester tester) async {
- controller.dispose();
- expect(stream.isClosed, isTrue);
- expect(controller.widget, isNull);
+ group('dispose', () {
+ testWidgets('closes the stream and removes the widget',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(stream.isClosed, isTrue);
+ expect(controller.widget, isNull);
+ });
+
+ testWidgets('cannot call getVisibleRegion after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() async {
+ await controller.getVisibleRegion();
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call getScreenCoordinate after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() async {
+ await controller.getScreenCoordinate(
+ LatLng(43.3072465, -5.6918241),
+ );
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call getLatLng after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() async {
+ await controller.getLatLng(
+ ScreenCoordinate(x: 640, y: 480),
+ );
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call moveCamera after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() async {
+ await controller.moveCamera(CameraUpdate.zoomIn());
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call getZoomLevel after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() async {
+ await controller.getZoomLevel();
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot updateCircles after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updateCircles(CircleUpdates.from({}, {}));
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot updatePolygons after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updatePolygons(PolygonUpdates.from({}, {}));
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot updatePolylines after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updatePolylines(PolylineUpdates.from({}, {}));
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot updateMarkers after dispose',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(() {
+ controller.updateMarkers(MarkerUpdates.from({}, {}));
+ }, throwsAssertionError);
+
+ expect(() {
+ controller.showInfoWindow(MarkerId('any'));
+ }, throwsAssertionError);
+
+ expect(() {
+ controller.hideInfoWindow(MarkerId('any'));
+ }, throwsAssertionError);
+ });
+
+ testWidgets('isInfoWindowShown defaults to false',
+ (WidgetTester tester) async {
+ controller.dispose();
+
+ expect(controller.isInfoWindowShown(MarkerId('any')), false);
+ });
});
});
group('init', () {
- _MockCirclesController circles;
- _MockMarkersController markers;
- _MockPolygonsController polygons;
- _MockPolylinesController polylines;
- _MockGMap map;
+ late MockCirclesController circles;
+ late MockMarkersController markers;
+ late MockPolygonsController polygons;
+ late MockPolylinesController polylines;
+ late gmaps.GMap map;
setUp(() {
- circles = _MockCirclesController();
- markers = _MockMarkersController();
- polygons = _MockPolygonsController();
- polylines = _MockPolylinesController();
- map = _MockGMap();
+ circles = MockCirclesController();
+ markers = MockMarkersController();
+ polygons = MockPolygonsController();
+ polylines = MockPolylinesController();
+ map = gmaps.GMap(html.DivElement());
});
testWidgets('listens to map events', (WidgetTester tester) async {
@@ -123,17 +216,25 @@
polylines: polylines,
);
- expect(map.onClickController.hasListener, isFalse);
- expect(map.onRightclickController.hasListener, isFalse);
- expect(map.onBoundsChangedController.hasListener, isFalse);
- expect(map.onIdleController.hasListener, isFalse);
-
controller.init();
- expect(map.onClickController.hasListener, isTrue);
- expect(map.onRightclickController.hasListener, isTrue);
- expect(map.onBoundsChangedController.hasListener, isTrue);
- expect(map.onIdleController.hasListener, isTrue);
+ // Trigger events on the map, and verify they've been broadcast to the stream
+ final capturedEvents = stream.stream.take(5);
+
+ gmaps.Event.trigger(
+ map, 'click', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)]);
+ gmaps.Event.trigger(map, 'rightclick',
+ [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)]);
+ gmaps.Event.trigger(map, 'bounds_changed', []); // Causes 2 events
+ gmaps.Event.trigger(map, 'idle', []);
+
+ final events = await capturedEvents.toList();
+
+ expect(events[0], isA<MapTapEvent>());
+ expect(events[1], isA<MapLongPressEvent>());
+ expect(events[2], isA<CameraMoveStartedEvent>());
+ expect(events[3], isA<CameraMoveEvent>());
+ expect(events[4], isA<CameraIdleEvent>());
});
testWidgets('binds geometry controllers to map\'s',
@@ -227,7 +328,7 @@
testWidgets('empty infoWindow does not create InfoWindow instance.',
(WidgetTester tester) async {
controller = _createController(markers: {
- Marker(markerId: MarkerId('marker-1'), infoWindow: null),
+ Marker(markerId: MarkerId('marker-1')),
});
controller.debugSetOverrides(
@@ -239,11 +340,11 @@
final capturedMarkers =
verify(markers.addMarkers(captureAny)).captured[0] as Set<Marker>;
- expect(capturedMarkers.first.infoWindow, isNull);
+ expect(capturedMarkers.first.infoWindow, InfoWindow.noText);
});
group('Initialization options', () {
- gmaps.MapOptions capturedOptions;
+ gmaps.MapOptions? capturedOptions;
setUp(() {
capturedOptions = null;
});
@@ -260,9 +361,9 @@
controller.init();
expect(capturedOptions, isNotNull);
- expect(capturedOptions.mapTypeId, gmaps.MapTypeId.SATELLITE);
- expect(capturedOptions.zoomControl, true);
- expect(capturedOptions.gestureHandling, 'auto',
+ expect(capturedOptions!.mapTypeId, gmaps.MapTypeId.SATELLITE);
+ expect(capturedOptions!.zoomControl, true);
+ expect(capturedOptions!.gestureHandling, 'auto',
reason:
'by default the map handles zoom/pan gestures internally');
});
@@ -280,7 +381,7 @@
controller.init();
expect(capturedOptions, isNotNull);
- expect(capturedOptions.gestureHandling, 'none',
+ expect(capturedOptions!.gestureHandling, 'none',
reason:
'disabling scroll gestures disables all gesture handling');
});
@@ -298,29 +399,11 @@
controller.init();
expect(capturedOptions, isNotNull);
- expect(capturedOptions.gestureHandling, 'none',
+ expect(capturedOptions!.gestureHandling, 'none',
reason:
'disabling scroll gestures disables all gesture handling');
});
- testWidgets('does not set initial position if absent',
- (WidgetTester tester) async {
- controller = _createController(
- initialCameraPosition: null,
- );
-
- controller.debugSetOverrides(createMap: (_, options) {
- capturedOptions = options;
- return map;
- });
-
- controller.init();
-
- expect(capturedOptions, isNotNull);
- expect(capturedOptions.zoom, isNull);
- expect(capturedOptions.center, isNull);
- });
-
testWidgets('sets initial position when passed',
(WidgetTester tester) async {
controller = _createController(
@@ -340,8 +423,8 @@
controller.init();
expect(capturedOptions, isNotNull);
- expect(capturedOptions.zoom, 12);
- expect(capturedOptions.center, isNotNull);
+ expect(capturedOptions!.zoom, 12);
+ expect(capturedOptions!.center, isNotNull);
});
});
@@ -366,10 +449,15 @@
// These are the methods that are delegated to the gmaps.GMap object, that we can mock...
group('Map control methods', () {
- _MockGMap map;
+ late gmaps.GMap map;
setUp(() {
- map = _MockGMap();
+ map = gmaps.GMap(
+ html.DivElement(),
+ gmaps.MapOptions()
+ ..zoom = 10
+ ..center = gmaps.LatLng(0, 0),
+ );
controller = _createController();
controller.debugSetOverrides(createMap: (_, __) => map);
controller.init();
@@ -380,9 +468,8 @@
controller.updateRawOptions({
'mapType': 2,
});
- final options = verify(map.options = captureAny).captured[0];
- expect(options.mapTypeId, gmaps.MapTypeId.SATELLITE);
+ expect(map.mapTypeId, gmaps.MapTypeId.SATELLITE);
});
testWidgets('can turn on/off traffic', (WidgetTester tester) async {
@@ -404,17 +491,19 @@
group('viewport getters', () {
testWidgets('getVisibleRegion', (WidgetTester tester) async {
- await controller.getVisibleRegion();
+ final gmCenter = map.center!;
+ final center =
+ LatLng(gmCenter.lat.toDouble(), gmCenter.lng.toDouble());
- verify(map.bounds);
+ final bounds = await controller.getVisibleRegion();
+
+ expect(bounds.contains(center), isTrue,
+ reason:
+ 'The computed visible region must contain the center of the created map.');
});
testWidgets('getZoomLevel', (WidgetTester tester) async {
- when(map.zoom).thenReturn(10);
-
- await controller.getZoomLevel();
-
- verify(map.zoom);
+ expect(await controller.getZoomLevel(), map.zoom);
});
});
@@ -423,10 +512,11 @@
await (controller
.moveCamera(CameraUpdate.newLatLngZoom(LatLng(19, 26), 12)));
- verify(map.zoom = 12);
- final captured = verify(map.panTo(captureAny)).captured[0];
- expect(captured.lat, 19);
- expect(captured.lng, 26);
+ final gmCenter = map.center!;
+
+ expect(map.zoom, 12);
+ expect(gmCenter.lat, closeTo(19, _acceptableDelta));
+ expect(gmCenter.lng, closeTo(26, _acceptableDelta));
});
});
@@ -445,7 +535,7 @@
});
testWidgets('updateCircles', (WidgetTester tester) async {
- final mock = _MockCirclesController();
+ final mock = MockCirclesController();
controller.debugSetOverrides(circles: mock);
final previous = {
@@ -472,7 +562,7 @@
});
testWidgets('updateMarkers', (WidgetTester tester) async {
- final mock = _MockMarkersController();
+ final mock = MockMarkersController();
controller.debugSetOverrides(markers: mock);
final previous = {
@@ -499,7 +589,7 @@
});
testWidgets('updatePolygons', (WidgetTester tester) async {
- final mock = _MockPolygonsController();
+ final mock = MockPolygonsController();
controller.debugSetOverrides(polygons: mock);
final previous = {
@@ -526,7 +616,7 @@
});
testWidgets('updatePolylines', (WidgetTester tester) async {
- final mock = _MockPolylinesController();
+ final mock = MockPolylinesController();
controller.debugSetOverrides(polylines: mock);
final previous = {
@@ -553,9 +643,10 @@
});
testWidgets('infoWindow visibility', (WidgetTester tester) async {
- final mock = _MockMarkersController();
- controller.debugSetOverrides(markers: mock);
+ final mock = MockMarkersController();
final markerId = MarkerId('marker-with-infowindow');
+ when(mock.isInfoWindowShown(markerId)).thenReturn(true);
+ controller.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
new file mode 100644
index 0000000..4793328
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart
@@ -0,0 +1,202 @@
+// 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.
+
+// Mocks generated by Mockito 5.0.2 from annotations
+// in google_maps_flutter_web_integration_tests/integration_test/google_maps_controller_test.dart.
+// Do not manually edit this file.
+
+import 'package:google_maps/src/generated/google_maps_core.js.g.dart' as _i2;
+import 'package:google_maps_flutter_platform_interface/src/types/circle.dart'
+ as _i4;
+import 'package:google_maps_flutter_platform_interface/src/types/marker.dart'
+ as _i7;
+import 'package:google_maps_flutter_platform_interface/src/types/polygon.dart'
+ as _i5;
+import 'package:google_maps_flutter_platform_interface/src/types/polyline.dart'
+ as _i6;
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i3;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeGMap extends _i1.Fake implements _i2.GMap {}
+
+/// A class which mocks [CirclesController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
+ @override
+ Map<_i4.CircleId, _i3.CircleController> get circles =>
+ (super.noSuchMethod(Invocation.getter(#circles),
+ returnValue: <_i4.CircleId, _i3.CircleController>{})
+ as Map<_i4.CircleId, _i3.CircleController>);
+ @override
+ _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
+ returnValue: _FakeGMap()) as _i2.GMap);
+ @override
+ set googleMap(_i2.GMap? _googleMap) =>
+ super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
+ returnValueForMissingStub: null);
+ @override
+ int get mapId =>
+ (super.noSuchMethod(Invocation.getter(#mapId), returnValue: 0) as int);
+ @override
+ set mapId(int? _mapId) =>
+ super.noSuchMethod(Invocation.setter(#mapId, _mapId),
+ returnValueForMissingStub: null);
+ @override
+ void addCircles(Set<_i4.Circle>? circlesToAdd) =>
+ super.noSuchMethod(Invocation.method(#addCircles, [circlesToAdd]),
+ returnValueForMissingStub: null);
+ @override
+ void changeCircles(Set<_i4.Circle>? circlesToChange) =>
+ super.noSuchMethod(Invocation.method(#changeCircles, [circlesToChange]),
+ returnValueForMissingStub: null);
+ @override
+ void removeCircles(Set<_i4.CircleId>? circleIdsToRemove) =>
+ super.noSuchMethod(Invocation.method(#removeCircles, [circleIdsToRemove]),
+ returnValueForMissingStub: null);
+ @override
+ void bindToMap(int? mapId, _i2.GMap? googleMap) =>
+ super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
+ returnValueForMissingStub: null);
+}
+
+/// A class which mocks [PolygonsController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockPolygonsController extends _i1.Mock
+ implements _i3.PolygonsController {
+ @override
+ Map<_i5.PolygonId, _i3.PolygonController> get polygons =>
+ (super.noSuchMethod(Invocation.getter(#polygons),
+ returnValue: <_i5.PolygonId, _i3.PolygonController>{})
+ as Map<_i5.PolygonId, _i3.PolygonController>);
+ @override
+ _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
+ returnValue: _FakeGMap()) as _i2.GMap);
+ @override
+ set googleMap(_i2.GMap? _googleMap) =>
+ super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
+ returnValueForMissingStub: null);
+ @override
+ int get mapId =>
+ (super.noSuchMethod(Invocation.getter(#mapId), returnValue: 0) as int);
+ @override
+ set mapId(int? _mapId) =>
+ super.noSuchMethod(Invocation.setter(#mapId, _mapId),
+ returnValueForMissingStub: null);
+ @override
+ void addPolygons(Set<_i5.Polygon>? polygonsToAdd) =>
+ super.noSuchMethod(Invocation.method(#addPolygons, [polygonsToAdd]),
+ returnValueForMissingStub: null);
+ @override
+ void changePolygons(Set<_i5.Polygon>? polygonsToChange) =>
+ super.noSuchMethod(Invocation.method(#changePolygons, [polygonsToChange]),
+ returnValueForMissingStub: null);
+ @override
+ void removePolygons(Set<_i5.PolygonId>? polygonIdsToRemove) => super
+ .noSuchMethod(Invocation.method(#removePolygons, [polygonIdsToRemove]),
+ returnValueForMissingStub: null);
+ @override
+ void bindToMap(int? mapId, _i2.GMap? googleMap) =>
+ super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
+ returnValueForMissingStub: null);
+}
+
+/// A class which mocks [PolylinesController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockPolylinesController extends _i1.Mock
+ implements _i3.PolylinesController {
+ @override
+ Map<_i6.PolylineId, _i3.PolylineController> get lines =>
+ (super.noSuchMethod(Invocation.getter(#lines),
+ returnValue: <_i6.PolylineId, _i3.PolylineController>{})
+ as Map<_i6.PolylineId, _i3.PolylineController>);
+ @override
+ _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
+ returnValue: _FakeGMap()) as _i2.GMap);
+ @override
+ set googleMap(_i2.GMap? _googleMap) =>
+ super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
+ returnValueForMissingStub: null);
+ @override
+ int get mapId =>
+ (super.noSuchMethod(Invocation.getter(#mapId), returnValue: 0) as int);
+ @override
+ set mapId(int? _mapId) =>
+ super.noSuchMethod(Invocation.setter(#mapId, _mapId),
+ returnValueForMissingStub: null);
+ @override
+ void addPolylines(Set<_i6.Polyline>? polylinesToAdd) =>
+ super.noSuchMethod(Invocation.method(#addPolylines, [polylinesToAdd]),
+ returnValueForMissingStub: null);
+ @override
+ void changePolylines(Set<_i6.Polyline>? polylinesToChange) => super
+ .noSuchMethod(Invocation.method(#changePolylines, [polylinesToChange]),
+ returnValueForMissingStub: null);
+ @override
+ void removePolylines(Set<_i6.PolylineId>? polylineIdsToRemove) => super
+ .noSuchMethod(Invocation.method(#removePolylines, [polylineIdsToRemove]),
+ returnValueForMissingStub: null);
+ @override
+ void bindToMap(int? mapId, _i2.GMap? googleMap) =>
+ super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
+ returnValueForMissingStub: null);
+}
+
+/// A class which mocks [MarkersController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockMarkersController extends _i1.Mock implements _i3.MarkersController {
+ @override
+ Map<_i7.MarkerId, _i3.MarkerController> get markers =>
+ (super.noSuchMethod(Invocation.getter(#markers),
+ returnValue: <_i7.MarkerId, _i3.MarkerController>{})
+ as Map<_i7.MarkerId, _i3.MarkerController>);
+ @override
+ _i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
+ returnValue: _FakeGMap()) as _i2.GMap);
+ @override
+ set googleMap(_i2.GMap? _googleMap) =>
+ super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
+ returnValueForMissingStub: null);
+ @override
+ int get mapId =>
+ (super.noSuchMethod(Invocation.getter(#mapId), returnValue: 0) as int);
+ @override
+ set mapId(int? _mapId) =>
+ super.noSuchMethod(Invocation.setter(#mapId, _mapId),
+ returnValueForMissingStub: null);
+ @override
+ void addMarkers(Set<_i7.Marker>? markersToAdd) =>
+ super.noSuchMethod(Invocation.method(#addMarkers, [markersToAdd]),
+ returnValueForMissingStub: null);
+ @override
+ void changeMarkers(Set<_i7.Marker>? markersToChange) =>
+ super.noSuchMethod(Invocation.method(#changeMarkers, [markersToChange]),
+ returnValueForMissingStub: null);
+ @override
+ void removeMarkers(Set<_i7.MarkerId>? markerIdsToRemove) =>
+ super.noSuchMethod(Invocation.method(#removeMarkers, [markerIdsToRemove]),
+ returnValueForMissingStub: null);
+ @override
+ void showMarkerInfoWindow(_i7.MarkerId? markerId) =>
+ super.noSuchMethod(Invocation.method(#showMarkerInfoWindow, [markerId]),
+ returnValueForMissingStub: null);
+ @override
+ void hideMarkerInfoWindow(_i7.MarkerId? markerId) =>
+ super.noSuchMethod(Invocation.method(#hideMarkerInfoWindow, [markerId]),
+ returnValueForMissingStub: null);
+ @override
+ bool isInfoWindowShown(_i7.MarkerId? markerId) =>
+ (super.noSuchMethod(Invocation.method(#isInfoWindowShown, [markerId]),
+ returnValue: false) as bool);
+ @override
+ void bindToMap(int? mapId, _i2.GMap? googleMap) =>
+ super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
+ 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 dee3761..2de431a 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
@@ -2,36 +2,40 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
+import 'dart:js_util' show getProperty;
import 'package:integration_test/integration_test.dart';
import 'package:flutter/widgets.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
-class _MockGoogleMapController extends Mock implements GoogleMapController {}
+import 'google_maps_plugin_test.mocks.dart';
+
+@GenerateMocks([], customMocks: [
+ MockSpec<GoogleMapController>(returnNullOnMissingStub: true),
+])
/// Test GoogleMapsPlugin
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('GoogleMapsPlugin', () {
- _MockGoogleMapController controller;
- GoogleMapsPlugin plugin;
- int reportedMapId;
+ late MockGoogleMapController controller;
+ late GoogleMapsPlugin plugin;
+ int? reportedMapId;
void onPlatformViewCreated(int id) {
reportedMapId = id;
}
setUp(() {
- controller = _MockGoogleMapController();
+ controller = MockGoogleMapController();
plugin = GoogleMapsPlugin();
reportedMapId = null;
});
@@ -72,34 +76,21 @@
final testMapId = 33930;
final initialCameraPosition = CameraPosition(target: LatLng(0, 0));
- testWidgets('throws without _webOnlyMapCreationId',
- (WidgetTester tester) async {
- expect(
- () => plugin.buildView(
- null,
- onPlatformViewCreated,
- initialCameraPosition: initialCameraPosition,
- ),
- throwsAssertionError,
- reason:
- '_webOnlyMapCreationId is mandatory to prevent unnecessary reloads in web.',
- );
- });
-
testWidgets(
'returns an HtmlElementView and caches the controller for later',
(WidgetTester tester) async {
final Map<int, GoogleMapController> cache = {};
plugin.debugSetMapById(cache);
- final HtmlElementView widget = plugin.buildView(
+ final Widget widget = plugin.buildView(
testMapId,
onPlatformViewCreated,
initialCameraPosition: initialCameraPosition,
);
+ expect(widget, isA<HtmlElementView>());
expect(
- widget.viewType,
+ (widget as HtmlElementView).viewType,
contains('$testMapId'),
reason:
'view type should contain the mapId passed when creating the map.',
@@ -160,11 +151,10 @@
expect(styles.length, 1);
// Let's peek inside the styles...
var style = styles[0] as gmaps.MapTypeStyle;
- expect(style.featureType, gmaps.MapTypeStyleFeatureType.POI_PARK);
- expect(
- style.elementType, gmaps.MapTypeStyleElementType.LABELS_TEXT_FILL);
- expect(style.stylers.length, 1);
- expect(style.stylers[0].color, '#6b9a76');
+ expect(style.featureType, 'poi.park');
+ expect(style.elementType, 'labels.text.fill');
+ expect(style.stylers?.length, 1);
+ expect(getProperty(style.stylers![0]!, 'color'), '#6b9a76');
});
});
@@ -247,31 +237,50 @@
verify(controller.moveCamera(expectedUpdates));
});
+
// Viewport
testWidgets('getVisibleRegion', (WidgetTester tester) async {
+ when(controller.getVisibleRegion())
+ .thenAnswer((_) async => LatLngBounds(
+ northeast: LatLng(47.2359634, -68.0192019),
+ southwest: LatLng(34.5019594, -120.4974629),
+ ));
await plugin.getVisibleRegion(mapId: mapId);
verify(controller.getVisibleRegion());
});
+
testWidgets('getZoomLevel', (WidgetTester tester) async {
+ when(controller.getZoomLevel()).thenAnswer((_) async => 10);
await plugin.getZoomLevel(mapId: mapId);
verify(controller.getZoomLevel());
});
+
testWidgets('getScreenCoordinate', (WidgetTester tester) async {
+ when(controller.getScreenCoordinate(any)).thenAnswer(
+ (_) async => ScreenCoordinate(x: 320, y: 240) // fake return
+ );
+
final latLng = LatLng(43.3613, -5.8499);
await plugin.getScreenCoordinate(latLng, mapId: mapId);
verify(controller.getScreenCoordinate(latLng));
});
+
testWidgets('getLatLng', (WidgetTester tester) async {
+ when(controller.getLatLng(any))
+ .thenAnswer((_) async => LatLng(43.3613, -5.8499) // fake return
+ );
+
final coordinates = ScreenCoordinate(x: 19, y: 26);
await plugin.getLatLng(coordinates, mapId: mapId);
verify(controller.getLatLng(coordinates));
});
+
// InfoWindows
testWidgets('showMarkerInfoWindow', (WidgetTester tester) async {
final markerId = MarkerId('testing-123');
@@ -280,6 +289,7 @@
verify(controller.showInfoWindow(markerId));
});
+
testWidgets('hideMarkerInfoWindow', (WidgetTester tester) async {
final markerId = MarkerId('testing-123');
@@ -287,7 +297,10 @@
verify(controller.hideInfoWindow(markerId));
});
+
testWidgets('isMarkerInfoWindowShown', (WidgetTester tester) async {
+ when(controller.isInfoWindowShown(any)).thenReturn(true);
+
final markerId = MarkerId('testing-123');
await plugin.isMarkerInfoWindowShown(markerId, mapId: mapId);
@@ -299,7 +312,7 @@
// Verify all event streams are filtered correctly from the main one...
group('Event Streams', () {
int mapId = 0;
- StreamController<MapEvent> streamController;
+ late StreamController<MapEvent> streamController;
setUp(() {
streamController = StreamController<MapEvent>.broadcast();
when(controller.events)
@@ -308,7 +321,8 @@
});
// Dispatches a few events in the global streamController, and expects *only* the passed event to be there.
- void _testStreamFiltering(Stream<MapEvent> stream, MapEvent event) async {
+ Future<void> _testStreamFiltering(
+ Stream<MapEvent> stream, MapEvent event) async {
Timer.run(() {
streamController.add(_OtherMapEvent(mapId));
streamController.add(event);
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
new file mode 100644
index 0000000..43150f6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart
@@ -0,0 +1,106 @@
+// 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.
+
+// Mocks generated by Mockito 5.0.2 from annotations
+// in google_maps_flutter_web_integration_tests/integration_test/google_maps_plugin_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i5;
+
+import 'package:google_maps_flutter_platform_interface/src/events/map_event.dart'
+ as _i6;
+import 'package:google_maps_flutter_platform_interface/src/types/camera.dart'
+ as _i7;
+import 'package:google_maps_flutter_platform_interface/src/types/circle_updates.dart'
+ as _i8;
+import 'package:google_maps_flutter_platform_interface/src/types/location.dart'
+ as _i2;
+import 'package:google_maps_flutter_platform_interface/src/types/marker.dart'
+ as _i12;
+import 'package:google_maps_flutter_platform_interface/src/types/marker_updates.dart'
+ as _i11;
+import 'package:google_maps_flutter_platform_interface/src/types/polygon_updates.dart'
+ as _i9;
+import 'package:google_maps_flutter_platform_interface/src/types/polyline_updates.dart'
+ as _i10;
+import 'package:google_maps_flutter_platform_interface/src/types/screen_coordinate.dart'
+ as _i3;
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i4;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeLatLngBounds extends _i1.Fake implements _i2.LatLngBounds {}
+
+class _FakeScreenCoordinate extends _i1.Fake implements _i3.ScreenCoordinate {}
+
+class _FakeLatLng extends _i1.Fake implements _i2.LatLng {}
+
+/// A class which mocks [GoogleMapController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockGoogleMapController extends _i1.Mock
+ implements _i4.GoogleMapController {
+ @override
+ _i5.Stream<_i6.MapEvent<dynamic>> get events =>
+ (super.noSuchMethod(Invocation.getter(#events),
+ returnValue: Stream<_i6.MapEvent<dynamic>>.empty())
+ as _i5.Stream<_i6.MapEvent<dynamic>>);
+ @override
+ void updateRawOptions(Map<String, dynamic>? optionsUpdate) =>
+ super.noSuchMethod(Invocation.method(#updateRawOptions, [optionsUpdate]),
+ returnValueForMissingStub: null);
+ @override
+ _i5.Future<_i2.LatLngBounds> getVisibleRegion() =>
+ (super.noSuchMethod(Invocation.method(#getVisibleRegion, []),
+ returnValue: Future.value(_FakeLatLngBounds()))
+ as _i5.Future<_i2.LatLngBounds>);
+ @override
+ _i5.Future<_i3.ScreenCoordinate> getScreenCoordinate(_i2.LatLng? latLng) =>
+ (super.noSuchMethod(Invocation.method(#getScreenCoordinate, [latLng]),
+ returnValue: Future.value(_FakeScreenCoordinate()))
+ as _i5.Future<_i3.ScreenCoordinate>);
+ @override
+ _i5.Future<_i2.LatLng> getLatLng(_i3.ScreenCoordinate? screenCoordinate) =>
+ (super.noSuchMethod(Invocation.method(#getLatLng, [screenCoordinate]),
+ returnValue: Future.value(_FakeLatLng())) as _i5.Future<_i2.LatLng>);
+ @override
+ _i5.Future<void> moveCamera(_i7.CameraUpdate? cameraUpdate) =>
+ (super.noSuchMethod(Invocation.method(#moveCamera, [cameraUpdate]),
+ returnValue: Future.value(null),
+ returnValueForMissingStub: Future.value()) as _i5.Future<void>);
+ @override
+ _i5.Future<double> getZoomLevel() =>
+ (super.noSuchMethod(Invocation.method(#getZoomLevel, []),
+ returnValue: Future.value(0.0)) as _i5.Future<double>);
+ @override
+ void updateCircles(_i8.CircleUpdates? updates) =>
+ super.noSuchMethod(Invocation.method(#updateCircles, [updates]),
+ returnValueForMissingStub: null);
+ @override
+ void updatePolygons(_i9.PolygonUpdates? updates) =>
+ super.noSuchMethod(Invocation.method(#updatePolygons, [updates]),
+ returnValueForMissingStub: null);
+ @override
+ void updatePolylines(_i10.PolylineUpdates? updates) =>
+ super.noSuchMethod(Invocation.method(#updatePolylines, [updates]),
+ returnValueForMissingStub: null);
+ @override
+ void updateMarkers(_i11.MarkerUpdates? updates) =>
+ super.noSuchMethod(Invocation.method(#updateMarkers, [updates]),
+ returnValueForMissingStub: null);
+ @override
+ void showInfoWindow(_i12.MarkerId? markerId) =>
+ super.noSuchMethod(Invocation.method(#showInfoWindow, [markerId]),
+ returnValueForMissingStub: null);
+ @override
+ void hideInfoWindow(_i12.MarkerId? markerId) =>
+ super.noSuchMethod(Invocation.method(#hideInfoWindow, [markerId]),
+ returnValueForMissingStub: null);
+ @override
+ bool isInfoWindowShown(_i12.MarkerId? markerId) =>
+ (super.noSuchMethod(Invocation.method(#isInfoWindowShown, [markerId]),
+ returnValue: false) as bool);
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart
index 1a85f3b..2bfa27b 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart
@@ -2,100 +2,157 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
+import 'dart:html' as html;
import 'package:integration_test/integration_test.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-
-class _MockMarker extends Mock implements gmaps.Marker {
- final onClickController = StreamController<gmaps.MouseEvent>();
- final onDragEndController = StreamController<gmaps.MouseEvent>();
-
- @override
- Stream<gmaps.MouseEvent> get onClick => onClickController.stream;
-
- @override
- Stream<gmaps.MouseEvent> get onDragend => onDragEndController.stream;
-}
-
-class _MockMouseEvent extends Mock implements gmaps.MouseEvent {}
-
-class _MockInfoWindow extends Mock implements gmaps.InfoWindow {}
/// Test Markers
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- bool called = false;
+ // Since onTap/DragEnd events happen asynchronously, we need to store when the event
+ // is fired. We use a completer so the test can wait for the future to be completed.
+ late Completer<bool> _methodCalledCompleter;
+
+ /// This is the future value of the [_methodCalledCompleter]. Reinitialized
+ /// in the [setUp] method, and completed (as `true`) by [onTap] and [onDragEnd]
+ /// when those methods are called from the MarkerController.
+ late Future<bool> methodCalled;
+
void onTap() {
- called = true;
+ _methodCalledCompleter.complete(true);
}
void onDragEnd(gmaps.LatLng _) {
- called = true;
+ _methodCalledCompleter.complete(true);
}
setUp(() {
- called = false;
+ _methodCalledCompleter = Completer();
+ methodCalled = _methodCalledCompleter.future;
});
group('MarkerController', () {
- _MockMarker marker;
+ late gmaps.Marker marker;
setUp(() {
- marker = _MockMarker();
+ marker = gmaps.Marker();
});
testWidgets('onTap gets called', (WidgetTester tester) async {
MarkerController(marker: marker, onTap: onTap);
- // Simulate a click
- await marker.onClickController.add(null);
- expect(called, isTrue);
+
+ // Trigger a click event...
+ gmaps.Event.trigger(marker, 'click', [gmaps.MapMouseEvent()]);
+
+ // The event handling is now truly async. Wait for it...
+ expect(await methodCalled, isTrue);
});
testWidgets('onDragEnd gets called', (WidgetTester tester) async {
- when(marker.draggable).thenReturn(true);
MarkerController(marker: marker, onDragEnd: onDragEnd);
- // Simulate a drag end
- await marker.onDragEndController.add(_MockMouseEvent());
- expect(called, isTrue);
+
+ // Trigger a drag end event...
+ gmaps.Event.trigger(marker, 'dragend',
+ [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)]);
+
+ expect(await methodCalled, isTrue);
});
testWidgets('update', (WidgetTester tester) async {
final controller = MarkerController(marker: marker);
- final options = gmaps.MarkerOptions()..draggable = false;
+ final options = gmaps.MarkerOptions()..draggable = true;
+
+ expect(marker.draggable, isNull);
+
controller.update(options);
- verify(marker.options = options);
+
+ expect(marker.draggable, isTrue);
});
testWidgets('infoWindow null, showInfoWindow.',
(WidgetTester tester) async {
final controller = MarkerController(marker: marker);
+
controller.showInfoWindow();
+
expect(controller.infoWindowShown, isFalse);
});
testWidgets('showInfoWindow', (WidgetTester tester) async {
- final infoWindow = _MockInfoWindow();
+ final infoWindow = gmaps.InfoWindow();
+ final map = gmaps.GMap(html.DivElement());
+ marker.set('map', map);
final controller =
MarkerController(marker: marker, infoWindow: infoWindow);
+
controller.showInfoWindow();
- verify(infoWindow.open(any, any)).called(1);
+
+ expect(infoWindow.get('map'), map);
expect(controller.infoWindowShown, isTrue);
});
testWidgets('hideInfoWindow', (WidgetTester tester) async {
- final infoWindow = _MockInfoWindow();
+ final infoWindow = gmaps.InfoWindow();
+ final map = gmaps.GMap(html.DivElement());
+ marker.set('map', map);
final controller =
MarkerController(marker: marker, infoWindow: infoWindow);
+
controller.hideInfoWindow();
- verify(infoWindow.close()).called(1);
+
+ expect(infoWindow.get('map'), isNull);
expect(controller.infoWindowShown, isFalse);
});
+
+ group('remove', () {
+ late MarkerController controller;
+
+ setUp(() {
+ final infoWindow = gmaps.InfoWindow();
+ final map = gmaps.GMap(html.DivElement());
+ marker.set('map', map);
+ controller = MarkerController(marker: marker, infoWindow: infoWindow);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.marker, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final options = gmaps.MarkerOptions()..draggable = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call showInfoWindow after remove',
+ (WidgetTester tester) async {
+ controller.remove();
+
+ expect(() {
+ controller.showInfoWindow();
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call hideInfoWindow after remove',
+ (WidgetTester tester) async {
+ controller.remove();
+
+ expect(() {
+ controller.hideInfoWindow();
+ }, throwsAssertionError);
+ });
+ });
});
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart
index e9e458c..6f2bf61 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart
@@ -2,17 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
import 'dart:convert';
-import 'dart:html';
+import 'dart:html' as html;
+import 'dart:js_util' show getProperty;
-import 'package:http/http.dart' as http;
-import 'package:integration_test/integration_test.dart';
+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:flutter_test/flutter_test.dart';
+import 'package:http/http.dart' as http;
+import 'package:integration_test/integration_test.dart';
import 'resources/icon_image_base64.dart';
@@ -20,12 +20,15 @@
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('MarkersController', () {
- StreamController<MapEvent> stream;
- MarkersController controller;
+ late StreamController<MapEvent> events;
+ late MarkersController controller;
+ late gmaps.GMap map;
setUp(() {
- stream = StreamController<MapEvent>();
- controller = MarkersController(stream: stream);
+ events = StreamController<MapEvent>();
+ controller = MarkersController(stream: events);
+ map = gmaps.GMap(html.DivElement());
+ controller.bindToMap(123, map);
});
testWidgets('addMarkers', (WidgetTester tester) async {
@@ -48,7 +51,7 @@
};
controller.addMarkers(markers);
- expect(controller.markers[MarkerId('1')].marker.draggable, isFalse);
+ expect(controller.markers[MarkerId('1')]?.marker?.draggable, isFalse);
// Update the marker with radius 10
final updatedMarkers = {
@@ -57,7 +60,7 @@
controller.changeMarkers(updatedMarkers);
expect(controller.markers.length, 1);
- expect(controller.markers[MarkerId('1')].marker.draggable, isTrue);
+ expect(controller.markers[MarkerId('1')]?.marker?.draggable, isTrue);
});
testWidgets('removeMarkers', (WidgetTester tester) async {
@@ -95,15 +98,15 @@
controller.addMarkers(markers);
- expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isFalse);
controller.showMarkerInfoWindow(MarkerId('1'));
- expect(controller.markers[MarkerId('1')].infoWindowShown, isTrue);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isTrue);
controller.hideMarkerInfoWindow(MarkerId('1'));
- expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isFalse);
});
// https://github.com/flutter/flutter/issues/67380
@@ -121,33 +124,21 @@
};
controller.addMarkers(markers);
- expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
- expect(controller.markers[MarkerId('2')].infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('2')]?.infoWindowShown, isFalse);
controller.showMarkerInfoWindow(MarkerId('1'));
- expect(controller.markers[MarkerId('1')].infoWindowShown, isTrue);
- expect(controller.markers[MarkerId('2')].infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isTrue);
+ expect(controller.markers[MarkerId('2')]?.infoWindowShown, isFalse);
controller.showMarkerInfoWindow(MarkerId('2'));
- expect(controller.markers[MarkerId('1')].infoWindowShown, isFalse);
- expect(controller.markers[MarkerId('2')].infoWindowShown, isTrue);
+ expect(controller.markers[MarkerId('1')]?.infoWindowShown, isFalse);
+ expect(controller.markers[MarkerId('2')]?.infoWindowShown, isTrue);
});
- // https://github.com/flutter/flutter/issues/64938
- testWidgets('markers with icon:null work', (WidgetTester tester) async {
- final markers = {
- Marker(markerId: MarkerId('1'), icon: null),
- };
-
- controller.addMarkers(markers);
-
- expect(controller.markers.length, 1);
- expect(controller.markers[MarkerId('1')].marker.icon, isNull);
- });
-
- //
+ // https://github.com/flutter/flutter/issues/66622
testWidgets('markers with custom bitmap icon work',
(WidgetTester tester) async {
final bytes = Base64Decoder().convert(iconImageBase64);
@@ -159,11 +150,15 @@
controller.addMarkers(markers);
expect(controller.markers.length, 1);
- expect(controller.markers[MarkerId('1')].marker.icon, isNotNull);
- expect(controller.markers[MarkerId('1')].marker.icon.url,
- startsWith('blob:'));
+ expect(controller.markers[MarkerId('1')]?.marker?.icon, isNotNull);
- final blobUrl = controller.markers[MarkerId('1')].marker.icon.url;
+ final blobUrl = getProperty(
+ controller.markers[MarkerId('1')]!.marker!.icon!,
+ 'url',
+ );
+
+ expect(blobUrl, startsWith('blob:'));
+
final response = await http.get(Uri.parse(blobUrl));
expect(response.bodyBytes, bytes,
@@ -187,8 +182,8 @@
controller.addMarkers(markers);
expect(controller.markers.length, 1);
- final content =
- controller.markers[MarkerId('1')].infoWindow.content as HtmlElement;
+ final content = controller.markers[MarkerId('1')]?.infoWindow?.content
+ as html.HtmlElement;
expect(content.innerHtml, contains('title for test'));
expect(
content.innerHtml,
@@ -211,12 +206,12 @@
controller.addMarkers(markers);
expect(controller.markers.length, 1);
- final content =
- controller.markers[MarkerId('1')].infoWindow.content as HtmlElement;
+ final content = controller.markers[MarkerId('1')]?.infoWindow?.content
+ as html.HtmlElement;
content.click();
- final event = await stream.stream.first;
+ final event = await events.stream.first;
expect(event, isA<InfoWindowTapEvent>());
expect((event as InfoWindowTapEvent).value, equals(MarkerId('1')));
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
index 0c35197..547aaec 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart
@@ -2,114 +2,195 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
import 'package:integration_test/integration_test.dart';
import 'package:google_maps/google_maps.dart' as gmaps;
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
-
-class _MockCircle extends Mock implements gmaps.Circle {
- final onClickController = StreamController<gmaps.MouseEvent>();
- @override
- Stream<gmaps.MouseEvent> get onClick => onClickController.stream;
-}
-
-class _MockPolygon extends Mock implements gmaps.Polygon {
- final onClickController = StreamController<gmaps.PolyMouseEvent>();
- @override
- Stream<gmaps.PolyMouseEvent> get onClick => onClickController.stream;
-}
-
-class _MockPolyline extends Mock implements gmaps.Polyline {
- final onClickController = StreamController<gmaps.PolyMouseEvent>();
- @override
- Stream<gmaps.PolyMouseEvent> get onClick => onClickController.stream;
-}
/// Test Shapes (Circle, Polygon, Polyline)
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- bool called = false;
+ // Since onTap events happen asynchronously, we need to store when the event
+ // is fired. We use a completer so the test can wait for the future to be completed.
+ late Completer<bool> _methodCalledCompleter;
+
+ /// This is the future value of the [_methodCalledCompleter]. Reinitialized
+ /// in the [setUp] method, and completed (as `true`) by [onTap], when it gets
+ /// called by the corresponding Shape Controller.
+ late Future<bool> methodCalled;
+
void onTap() {
- called = true;
+ _methodCalledCompleter.complete(true);
}
setUp(() {
- called = false;
+ _methodCalledCompleter = Completer();
+ methodCalled = _methodCalledCompleter.future;
});
group('CircleController', () {
- _MockCircle circle;
+ late gmaps.Circle circle;
setUp(() {
- circle = _MockCircle();
+ circle = gmaps.Circle();
});
testWidgets('onTap gets called', (WidgetTester tester) async {
CircleController(circle: circle, consumeTapEvents: true, onTap: onTap);
- expect(circle.onClickController.hasListener, isTrue);
- // Simulate a click
- await circle.onClickController.add(null);
- expect(called, isTrue);
+
+ // Trigger a click event...
+ gmaps.Event.trigger(circle, 'click', [gmaps.MapMouseEvent()]);
+
+ // The event handling is now truly async. Wait for it...
+ expect(await methodCalled, isTrue);
});
testWidgets('update', (WidgetTester tester) async {
final controller = CircleController(circle: circle);
- final options = gmaps.CircleOptions()..draggable = false;
+ final options = gmaps.CircleOptions()..draggable = true;
+
+ expect(circle.draggable, isNull);
+
controller.update(options);
- verify(circle.options = options);
+
+ expect(circle.draggable, isTrue);
+ });
+
+ group('remove', () {
+ late CircleController controller;
+
+ setUp(() {
+ controller = CircleController(circle: circle);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.circle, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final options = gmaps.CircleOptions()..draggable = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
});
});
group('PolygonController', () {
- _MockPolygon polygon;
+ late gmaps.Polygon polygon;
setUp(() {
- polygon = _MockPolygon();
+ polygon = gmaps.Polygon();
});
testWidgets('onTap gets called', (WidgetTester tester) async {
PolygonController(polygon: polygon, consumeTapEvents: true, onTap: onTap);
- expect(polygon.onClickController.hasListener, isTrue);
- // Simulate a click
- await polygon.onClickController.add(null);
- expect(called, isTrue);
+
+ // Trigger a click event...
+ gmaps.Event.trigger(polygon, 'click', [gmaps.MapMouseEvent()]);
+
+ // The event handling is now truly async. Wait for it...
+ expect(await methodCalled, isTrue);
});
testWidgets('update', (WidgetTester tester) async {
final controller = PolygonController(polygon: polygon);
- final options = gmaps.PolygonOptions()..draggable = false;
+ final options = gmaps.PolygonOptions()..draggable = true;
+
+ expect(polygon.draggable, isNull);
+
controller.update(options);
- verify(polygon.options = options);
+
+ expect(polygon.draggable, isTrue);
+ });
+
+ group('remove', () {
+ late PolygonController controller;
+
+ setUp(() {
+ controller = PolygonController(polygon: polygon);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.polygon, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final options = gmaps.PolygonOptions()..draggable = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
});
});
group('PolylineController', () {
- _MockPolyline polyline;
+ late gmaps.Polyline polyline;
setUp(() {
- polyline = _MockPolyline();
+ polyline = gmaps.Polyline();
});
testWidgets('onTap gets called', (WidgetTester tester) async {
PolylineController(
polyline: polyline, consumeTapEvents: true, onTap: onTap);
- expect(polyline.onClickController.hasListener, isTrue);
- // Simulate a click
- await polyline.onClickController.add(null);
- expect(called, isTrue);
+
+ // Trigger a click event...
+ gmaps.Event.trigger(polyline, 'click', [gmaps.MapMouseEvent()]);
+
+ // The event handling is now truly async. Wait for it...
+ expect(await methodCalled, isTrue);
});
testWidgets('update', (WidgetTester tester) async {
final controller = PolylineController(polyline: polyline);
- final options = gmaps.PolylineOptions()..draggable = false;
+ final options = gmaps.PolylineOptions()..draggable = true;
+
+ expect(polyline.draggable, isNull);
+
controller.update(options);
- verify(polyline.options = options);
+
+ expect(polyline.draggable, isTrue);
+ });
+
+ group('remove', () {
+ late PolylineController controller;
+
+ setUp(() {
+ controller = PolylineController(polyline: polyline);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.line, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final options = gmaps.PolylineOptions()..draggable = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
});
});
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
index 9bb3599..80b4e08 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// @dart = 2.9
-
import 'dart:async';
import 'dart:ui';
+import 'dart:html' as html;
import 'package:integration_test/integration_test.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
@@ -23,13 +22,20 @@
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ late gmaps.GMap map;
+
+ setUp(() {
+ map = gmaps.GMap(html.DivElement());
+ });
+
group('CirclesController', () {
- StreamController<MapEvent> stream;
- CirclesController controller;
+ late StreamController<MapEvent> events;
+ late CirclesController controller;
setUp(() {
- stream = StreamController<MapEvent>();
- controller = CirclesController(stream: stream);
+ events = StreamController<MapEvent>();
+ controller = CirclesController(stream: events);
+ controller.bindToMap(123, map);
});
testWidgets('addCircles', (WidgetTester tester) async {
@@ -52,7 +58,7 @@
};
controller.addCircles(circles);
- expect(controller.circles[CircleId('1')].circle.visible, isTrue);
+ expect(controller.circles[CircleId('1')]?.circle?.visible, isTrue);
final updatedCircles = {
Circle(circleId: CircleId('1'), visible: false),
@@ -60,7 +66,7 @@
controller.changeCircles(updatedCircles);
expect(controller.circles.length, 1);
- expect(controller.circles[CircleId('1')].circle.visible, isFalse);
+ expect(controller.circles[CircleId('1')]?.circle?.visible, isFalse);
});
testWidgets('removeCircles', (WidgetTester tester) async {
@@ -99,7 +105,7 @@
controller.addCircles(circles);
- final circle = controller.circles.values.first.circle;
+ final circle = controller.circles.values.first.circle!;
expect(circle.get('fillColor'), '#fabada');
expect(circle.get('fillOpacity'), closeTo(0.5, _acceptableDelta));
@@ -109,12 +115,13 @@
});
group('PolygonsController', () {
- StreamController<MapEvent> stream;
- PolygonsController controller;
+ late StreamController<MapEvent> events;
+ late PolygonsController controller;
setUp(() {
- stream = StreamController<MapEvent>();
- controller = PolygonsController(stream: stream);
+ events = StreamController<MapEvent>();
+ controller = PolygonsController(stream: events);
+ controller.bindToMap(123, map);
});
testWidgets('addPolygons', (WidgetTester tester) async {
@@ -137,7 +144,7 @@
};
controller.addPolygons(polygons);
- expect(controller.polygons[PolygonId('1')].polygon.visible, isTrue);
+ expect(controller.polygons[PolygonId('1')]?.polygon?.visible, isTrue);
// Update the polygon
final updatedPolygons = {
@@ -146,7 +153,7 @@
controller.changePolygons(updatedPolygons);
expect(controller.polygons.length, 1);
- expect(controller.polygons[PolygonId('1')].polygon.visible, isFalse);
+ expect(controller.polygons[PolygonId('1')]?.polygon?.visible, isFalse);
});
testWidgets('removePolygons', (WidgetTester tester) async {
@@ -185,7 +192,7 @@
controller.addPolygons(polygons);
- final polygon = controller.polygons.values.first.polygon;
+ final polygon = controller.polygons.values.first.polygon!;
expect(polygon.get('fillColor'), '#fabada');
expect(polygon.get('fillOpacity'), closeTo(0.5, _acceptableDelta));
@@ -243,7 +250,7 @@
final polygon = controller.polygons.values.first.polygon;
final pointInHole = gmaps.LatLng(28.632, -68.401);
- expect(geometry.poly.containsLocation(pointInHole, polygon), false);
+ expect(geometry.Poly.containsLocation(pointInHole, polygon), false);
});
testWidgets('Hole Path gets reversed to display correctly',
@@ -268,25 +275,22 @@
controller.addPolygons(polygons);
- expect(
- controller.polygons.values.first.polygon.paths.getAt(1).getAt(0).lat,
- 28.745);
- expect(
- controller.polygons.values.first.polygon.paths.getAt(1).getAt(1).lat,
- 29.57);
- expect(
- controller.polygons.values.first.polygon.paths.getAt(1).getAt(2).lat,
- 27.339);
+ final paths = controller.polygons.values.first.polygon!.paths!;
+
+ expect(paths.getAt(1)?.getAt(0)?.lat, 28.745);
+ expect(paths.getAt(1)?.getAt(1)?.lat, 29.57);
+ expect(paths.getAt(1)?.getAt(2)?.lat, 27.339);
});
});
group('PolylinesController', () {
- StreamController<MapEvent> stream;
- PolylinesController controller;
+ late StreamController<MapEvent> events;
+ late PolylinesController controller;
setUp(() {
- stream = StreamController<MapEvent>();
- controller = PolylinesController(stream: stream);
+ events = StreamController<MapEvent>();
+ controller = PolylinesController(stream: events);
+ controller.bindToMap(123, map);
});
testWidgets('addPolylines', (WidgetTester tester) async {
@@ -309,7 +313,7 @@
};
controller.addPolylines(polylines);
- expect(controller.lines[PolylineId('1')].line.visible, isTrue);
+ expect(controller.lines[PolylineId('1')]?.line?.visible, isTrue);
final updatedPolylines = {
Polyline(polylineId: PolylineId('1'), visible: false),
@@ -317,7 +321,7 @@
controller.changePolylines(updatedPolylines);
expect(controller.lines.length, 1);
- expect(controller.lines[PolylineId('1')].line.visible, isFalse);
+ expect(controller.lines[PolylineId('1')]?.line?.visible, isFalse);
});
testWidgets('removePolylines', (WidgetTester tester) async {
@@ -355,7 +359,7 @@
controller.addPolylines(lines);
- final line = controller.lines.values.first.line;
+ final line = controller.lines.values.first.line!;
expect(line.get('strokeColor'), '#fabada');
expect(line.get('strokeOpacity'), closeTo(0.5, _acceptableDelta));
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
index 2895501..311f05a 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml
@@ -1,9 +1,15 @@
name: google_maps_flutter_web_integration_tests
publish_to: none
+# Tests require flutter beta or greater to run.
environment:
- sdk: ">=2.2.2 <3.0.0" # Bump this to 2.12 when migrating to nnbd.
- flutter: ">=1.27.0-0" # For integration_test from sdk
+ sdk: ">=2.12.0 <3.0.0"
+ flutter: ">=2.0.0"
+ # Actually, Flutter 2.0.0 shipped with a slightly older version of integration_test that
+ # did *not* support null safety. This flutter constraint should be changed to >=2.1.0 when
+ # that version (or greater) is shipped to the `stable` channel.
+ # This causes integration tests to *not* work in `stable`, for now. Please, run integration tests
+ # in `beta` or newer (flutter/plugins runs these tests in `master`).
dependencies:
google_maps_flutter_web:
@@ -12,12 +18,19 @@
sdk: flutter
dev_dependencies:
- google_maps: ^3.4.4
+ build_runner: ^1.11.0
+ google_maps: ^5.1.0
http: ^0.13.0
- mockito: ^4.1.1+1 # Update to ^5.0.0 as soon as this migrates to null-safety
+ mockito: ^5.0.0
flutter_driver:
sdk: flutter
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
+
+# Remove these once environment flutter is set to ">=2.1.0".
+# Used to reconcile mockito ^5.0.0 with flutter 2.0.x (current stable).
+dependency_overrides:
+ crypto: ^3.0.0
+ convert: ^3.0.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/regen_mocks.sh b/packages/google_maps_flutter/google_maps_flutter_web/example/regen_mocks.sh
new file mode 100755
index 0000000..78bcdc0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/regen_mocks.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/bash
+# 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.
+
+flutter pub get
+
+echo "(Re)generating mocks."
+
+flutter pub run build_runner build --delete-conflicting-outputs
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/run_test.sh b/packages/google_maps_flutter/google_maps_flutter_web/example/run_test.sh
index aa52974..fcac5f6 100755
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/run_test.sh
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/run_test.sh
@@ -6,6 +6,8 @@
if pgrep -lf chromedriver > /dev/null; then
echo "chromedriver is running."
+ ./regen_mocks.sh
+
if [ $# -eq 0 ]; then
echo "No target specified, running all tests..."
find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}'
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
index f324d1c..3121d18 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html
@@ -12,4 +12,3 @@
<script src="main.dart.js"></script>
</body>
</html>
-
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 0aa563c..6dc2dab 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
@@ -6,6 +6,7 @@
import 'dart:async';
import 'dart:html';
+import 'dart:js_util';
import 'src/shims/dart_ui.dart' as ui; // Conditionally imports dart:ui in web
import 'dart:convert';
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circle.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circle.dart
index 84bae1b..65057d8 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circle.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circle.dart
@@ -6,15 +6,15 @@
/// The `CircleController` class wraps a [gmaps.Circle] and its `onTap` behavior.
class CircleController {
- gmaps.Circle _circle;
+ gmaps.Circle? _circle;
final bool _consumeTapEvents;
/// Creates a `CircleController`, which wraps a [gmaps.Circle] object and its `onTap` behavior.
CircleController({
- @required gmaps.Circle circle,
+ required gmaps.Circle circle,
bool consumeTapEvents = false,
- ui.VoidCallback onTap,
+ ui.VoidCallback? onTap,
}) : _circle = circle,
_consumeTapEvents = consumeTapEvents {
if (onTap != null) {
@@ -26,21 +26,26 @@
/// Returns the wrapped [gmaps.Circle]. Only used for testing.
@visibleForTesting
- gmaps.Circle get circle => _circle;
+ gmaps.Circle? get circle => _circle;
/// Returns `true` if this Controller will use its own `onTap` handler to consume events.
bool get consumeTapEvents => _consumeTapEvents;
/// Updates the options of the wrapped [gmaps.Circle] object.
+ ///
+ /// This cannot be called after [remove].
void update(gmaps.CircleOptions options) {
- _circle.options = options;
+ assert(_circle != null, 'Cannot `update` Circle after calling `remove`.');
+ _circle!.options = options;
}
/// Disposes of the currently wrapped [gmaps.Circle].
void remove() {
- _circle.visible = false;
- _circle.radius = 0;
- _circle.map = null;
- _circle = null;
+ if (_circle != null) {
+ _circle!.visible = false;
+ _circle!.radius = 0;
+ _circle!.map = null;
+ _circle = null;
+ }
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circles.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circles.dart
index 659d8ac..2a19d87 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circles.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/circles.dart
@@ -14,8 +14,8 @@
/// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
CirclesController({
- @required StreamController<MapEvent> stream,
- }) : _streamController = stream,
+ required StreamController<MapEvent> stream,
+ }) : _streamController = stream,
_circleIdToController = Map<CircleId, CircleController>();
/// Returns the cache of [CircleController]s. Test only.
@@ -26,7 +26,7 @@
///
/// Wraps each [Circle] into its corresponding [CircleController].
void addCircles(Set<Circle> circlesToAdd) {
- circlesToAdd?.forEach((circle) {
+ circlesToAdd.forEach((circle) {
_addCircle(circle);
});
}
@@ -50,20 +50,21 @@
/// Updates a set of [Circle] objects with new options.
void changeCircles(Set<Circle> circlesToChange) {
- circlesToChange?.forEach((circleToChange) {
+ circlesToChange.forEach((circleToChange) {
_changeCircle(circleToChange);
});
}
void _changeCircle(Circle circle) {
- final circleController = _circleIdToController[circle?.circleId];
+ final circleController = _circleIdToController[circle.circleId];
circleController?.update(_circleOptionsFromCircle(circle));
}
/// Removes a set of [CircleId]s from the cache.
void removeCircles(Set<CircleId> circleIdsToRemove) {
- circleIdsToRemove?.forEach((circleId) {
- final CircleController circleController = _circleIdToController[circleId];
+ circleIdsToRemove.forEach((circleId) {
+ final CircleController? circleController =
+ _circleIdToController[circleId];
circleController?.remove();
_circleIdToController.remove(circleId);
});
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
index c875bf7..2e71c79 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
@@ -4,11 +4,10 @@
part of google_maps_flutter_web;
-final _nullLatLng = LatLng(0, 0);
-final _nullLatLngBounds = LatLngBounds(
- northeast: _nullLatLng,
- southwest: _nullLatLng,
-);
+// Default values for when the gmaps objects return null/undefined values.
+final _nullGmapsLatLng = gmaps.LatLng(0, 0);
+final _nullGmapsLatLngBounds =
+ gmaps.LatLngBounds(_nullGmapsLatLng, _nullGmapsLatLng);
// Defaults taken from the Google Maps Platform SDK documentation.
final _defaultCssColor = '#000000';
@@ -115,80 +114,6 @@
return rawOptions['trafficEnabled'] ?? false;
}
-// Coverts the incoming JSON object into a List of MapTypeStyler objects.
-List<gmaps.MapTypeStyler> _parseStylers(List stylerJsons) {
- return stylerJsons?.map((styler) {
- return gmaps.MapTypeStyler()
- ..color = styler['color']
- ..gamma = styler['gamma']
- ..hue = styler['hue']
- ..invertLightness = styler['invertLightness']
- ..lightness = styler['lightness']
- ..saturation = styler['saturation']
- ..visibility = styler['visibility']
- ..weight = styler['weight'];
- })?.toList();
-}
-
-// Converts a String to its corresponding MapTypeStyleElementType enum value.
-final _elementTypeToEnum = <String, gmaps.MapTypeStyleElementType>{
- 'all': gmaps.MapTypeStyleElementType.ALL,
- 'geometry': gmaps.MapTypeStyleElementType.GEOMETRY,
- 'geometry.fill': gmaps.MapTypeStyleElementType.GEOMETRY_FILL,
- 'geometry.stroke': gmaps.MapTypeStyleElementType.GEOMETRY_STROKE,
- 'labels': gmaps.MapTypeStyleElementType.LABELS,
- 'labels.icon': gmaps.MapTypeStyleElementType.LABELS_ICON,
- 'labels.text': gmaps.MapTypeStyleElementType.LABELS_TEXT,
- 'labels.text.fill': gmaps.MapTypeStyleElementType.LABELS_TEXT_FILL,
- 'labels.text.stroke': gmaps.MapTypeStyleElementType.LABELS_TEXT_STROKE,
-};
-
-// Converts a String to its corresponding MapTypeStyleFeatureType enum value.
-final _featureTypeToEnum = <String, gmaps.MapTypeStyleFeatureType>{
- 'administrative': gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE,
- 'administrative.country':
- gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE_COUNTRY,
- 'administrative.land_parcel':
- gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE_LAND_PARCEL,
- 'administrative.locality':
- gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE_LOCALITY,
- 'administrative.neighborhood':
- gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE_NEIGHBORHOOD,
- 'administrative.province':
- gmaps.MapTypeStyleFeatureType.ADMINISTRATIVE_PROVINCE,
- 'all': gmaps.MapTypeStyleFeatureType.ALL,
- 'landscape': gmaps.MapTypeStyleFeatureType.LANDSCAPE,
- 'landscape.man_made': gmaps.MapTypeStyleFeatureType.LANDSCAPE_MAN_MADE,
- 'landscape.natural': gmaps.MapTypeStyleFeatureType.LANDSCAPE_NATURAL,
- 'landscape.natural.landcover':
- gmaps.MapTypeStyleFeatureType.LANDSCAPE_NATURAL_LANDCOVER,
- 'landscape.natural.terrain':
- gmaps.MapTypeStyleFeatureType.LANDSCAPE_NATURAL_TERRAIN,
- 'poi': gmaps.MapTypeStyleFeatureType.POI,
- 'poi.attraction': gmaps.MapTypeStyleFeatureType.POI_ATTRACTION,
- 'poi.business': gmaps.MapTypeStyleFeatureType.POI_BUSINESS,
- 'poi.government': gmaps.MapTypeStyleFeatureType.POI_GOVERNMENT,
- 'poi.medical': gmaps.MapTypeStyleFeatureType.POI_MEDICAL,
- 'poi.park': gmaps.MapTypeStyleFeatureType.POI_PARK,
- 'poi.place_of_worship': gmaps.MapTypeStyleFeatureType.POI_PLACE_OF_WORSHIP,
- 'poi.school': gmaps.MapTypeStyleFeatureType.POI_SCHOOL,
- 'poi.sports_complex': gmaps.MapTypeStyleFeatureType.POI_SPORTS_COMPLEX,
- 'road': gmaps.MapTypeStyleFeatureType.ROAD,
- 'road.arterial': gmaps.MapTypeStyleFeatureType.ROAD_ARTERIAL,
- 'road.highway': gmaps.MapTypeStyleFeatureType.ROAD_HIGHWAY,
- 'road.highway.controlled_access':
- gmaps.MapTypeStyleFeatureType.ROAD_HIGHWAY_CONTROLLED_ACCESS,
- 'road.local': gmaps.MapTypeStyleFeatureType.ROAD_LOCAL,
- 'transit': gmaps.MapTypeStyleFeatureType.TRANSIT,
- 'transit.line': gmaps.MapTypeStyleFeatureType.TRANSIT_LINE,
- 'transit.station': gmaps.MapTypeStyleFeatureType.TRANSIT_STATION,
- 'transit.station.airport':
- gmaps.MapTypeStyleFeatureType.TRANSIT_STATION_AIRPORT,
- 'transit.station.bus': gmaps.MapTypeStyleFeatureType.TRANSIT_STATION_BUS,
- 'transit.station.rail': gmaps.MapTypeStyleFeatureType.TRANSIT_STATION_RAIL,
- 'water': gmaps.MapTypeStyleFeatureType.WATER,
-};
-
// The keys we'd expect to see in a serialized MapTypeStyle JSON object.
final _mapStyleKeys = {
'elementType',
@@ -202,37 +127,36 @@
}
// Converts an incoming JSON-encoded Style info, into the correct gmaps array.
-List<gmaps.MapTypeStyle> _mapStyles(String mapStyleJson) {
+List<gmaps.MapTypeStyle> _mapStyles(String? mapStyleJson) {
List<gmaps.MapTypeStyle> styles = [];
if (mapStyleJson != null) {
- styles = json.decode(mapStyleJson, reviver: (key, value) {
- if (value is Map && _isJsonMapStyle(value)) {
- return gmaps.MapTypeStyle()
- ..elementType = _elementTypeToEnum[value['elementType']]
- ..featureType = _featureTypeToEnum[value['featureType']]
- ..stylers = _parseStylers(value['stylers']);
- }
- return value;
- }).cast<gmaps.MapTypeStyle>();
+ styles = json
+ .decode(mapStyleJson, reviver: (key, value) {
+ if (value is Map && _isJsonMapStyle(value)) {
+ return gmaps.MapTypeStyle()
+ ..elementType = value['elementType']
+ ..featureType = value['featureType']
+ ..stylers =
+ (value['stylers'] as List).map((e) => jsify(e)).toList();
+ }
+ return value;
+ })
+ .cast<gmaps.MapTypeStyle>()
+ .toList();
+ // .toList calls are required so the JS API understands the underlying data structure.
}
return styles;
}
gmaps.LatLng _latLngToGmLatLng(LatLng latLng) {
- if (latLng == null) return null;
return gmaps.LatLng(latLng.latitude, latLng.longitude);
}
LatLng _gmLatLngToLatLng(gmaps.LatLng latLng) {
- if (latLng == null) return _nullLatLng;
- return LatLng(latLng.lat, latLng.lng);
+ return LatLng(latLng.lat.toDouble(), latLng.lng.toDouble());
}
LatLngBounds _gmLatLngBoundsTolatLngBounds(gmaps.LatLngBounds latLngBounds) {
- if (latLngBounds == null) {
- return _nullLatLngBounds;
- }
-
return LatLngBounds(
southwest: _gmLatLngToLatLng(latLngBounds.southWest),
northeast: _gmLatLngToLatLng(latLngBounds.northEast),
@@ -241,10 +165,10 @@
CameraPosition _gmViewportToCameraPosition(gmaps.GMap map) {
return CameraPosition(
- target: _gmLatLngToLatLng(map.center),
- bearing: map.heading ?? 0,
- tilt: map.tilt ?? 0,
- zoom: map.zoom?.toDouble() ?? 10,
+ target: _gmLatLngToLatLng(map.center ?? _nullGmapsLatLng),
+ bearing: map.heading?.toDouble() ?? 0,
+ tilt: map.tilt?.toDouble() ?? 0,
+ zoom: map.zoom?.toDouble() ?? 0,
);
}
@@ -252,9 +176,13 @@
// TODO: Move to their appropriate objects, maybe make these copy constructors:
// Marker.fromMarker(anotherMarker, moreOptions);
-gmaps.InfoWindowOptions _infoWindowOptionsFromMarker(Marker marker) {
- if ((marker.infoWindow?.title?.isEmpty ?? true) &&
- (marker.infoWindow?.snippet?.isEmpty ?? true)) {
+gmaps.InfoWindowOptions? _infoWindowOptionsFromMarker(Marker marker) {
+ final markerTitle = marker.infoWindow.title ?? '';
+ final markerSnippet = marker.infoWindow.snippet ?? '';
+
+ // If both the title and snippet of an infowindow are empty, we don't really
+ // want an infowindow...
+ if ((markerTitle.isEmpty) && (markerSnippet.isEmpty)) {
return null;
}
@@ -262,17 +190,18 @@
// to click events...
final HtmlElement container = DivElement()
..id = 'gmaps-marker-${marker.markerId.value}-infowindow';
- if (marker.infoWindow.title?.isNotEmpty ?? false) {
+
+ if (markerTitle.isNotEmpty) {
final HtmlElement title = HeadingElement.h3()
..className = 'infowindow-title'
- ..innerText = marker.infoWindow.title;
+ ..innerText = markerTitle;
container.children.add(title);
}
- if (marker.infoWindow.snippet?.isNotEmpty ?? false) {
+ if (markerSnippet.isNotEmpty) {
final HtmlElement snippet = DivElement()
..className = 'infowindow-snippet'
..setInnerHtml(
- sanitizeHtml(marker.infoWindow.snippet),
+ sanitizeHtml(markerSnippet),
treeSanitizer: NodeTreeSanitizer.trusted,
);
container.children.add(snippet);
@@ -290,10 +219,10 @@
// Preserves the position from the [currentMarker], if set.
gmaps.MarkerOptions _markerOptionsFromMarker(
Marker marker,
- gmaps.Marker currentMarker,
+ gmaps.Marker? currentMarker,
) {
- final iconConfig = marker.icon?.toJson() as List;
- gmaps.Icon icon;
+ final iconConfig = marker.icon.toJson() as List;
+ gmaps.Icon? icon;
if (iconConfig != null) {
if (iconConfig[0] == 'fromAssetImage') {
@@ -324,7 +253,7 @@
marker.position.latitude,
marker.position.longitude,
)
- ..title = sanitizeHtml(marker.infoWindow?.title ?? "")
+ ..title = sanitizeHtml(marker.infoWindow.title ?? "")
..zIndex = marker.zIndex
..visible = marker.visible
..opacity = marker.alpha
@@ -356,7 +285,7 @@
final polygonDirection = _isPolygonClockwise(path);
List<List<gmaps.LatLng>> paths = [path];
int holeIndex = 0;
- polygon.holes?.forEach((hole) {
+ polygon.holes.forEach((hole) {
List<gmaps.LatLng> holePath =
hole.map((point) => _latLngToGmLatLng(point)).toList();
if (_isPolygonClockwise(holePath) == polygonDirection) {
@@ -387,6 +316,13 @@
/// based on: https://stackoverflow.com/a/1165943
///
/// returns [true] if clockwise [false] if counterclockwise
+///
+/// This method expects that the incoming [path] is a `List` of well-formed,
+/// non-null [gmaps.LatLng] objects.
+///
+/// Currently, this method is only called from [_polygonOptionsFromPolygon], and
+/// the `path` is a transformed version of [Polygon.points] or each of the
+/// [Polygon.holes], guaranteeing that `lat` and `lng` can be accessed with `!`.
bool _isPolygonClockwise(List<gmaps.LatLng> path) {
var direction = 0.0;
for (var i = 0; i < path.length; i++) {
@@ -447,7 +383,7 @@
map.panBy(json[1], json[2]);
break;
case 'zoomBy':
- gmaps.LatLng focusLatLng;
+ gmaps.LatLng? focusLatLng;
double zoomDelta = json[1] ?? 0;
// Web only supports integer changes...
int newZoomDelta = zoomDelta < 0 ? zoomDelta.floor() : zoomDelta.ceil();
@@ -460,16 +396,16 @@
// print('Error computing new focus LatLng. JS Error: ' + e.toString());
}
}
- map.zoom = map.zoom + newZoomDelta;
+ map.zoom = (map.zoom ?? 0) + newZoomDelta;
if (focusLatLng != null) {
map.panTo(focusLatLng);
}
break;
case 'zoomIn':
- map.zoom++;
+ map.zoom = (map.zoom ?? 0) + 1;
break;
case 'zoomOut':
- map.zoom--;
+ map.zoom = (map.zoom ?? 0) - 1;
break;
case 'zoomTo':
map.zoom = json[1];
@@ -481,17 +417,27 @@
// original JS by: Byron Singh (https://stackoverflow.com/a/30541162)
gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) {
- final ne = map.bounds.northEast;
- final sw = map.bounds.southWest;
+ final bounds = map.bounds;
final projection = map.projection;
+ final zoom = map.zoom;
- final topRight = projection.fromLatLngToPoint(ne);
- final bottomLeft = projection.fromLatLngToPoint(sw);
+ assert(
+ bounds != null, 'Map Bounds required to compute LatLng of screen x/y.');
+ assert(projection != null,
+ 'Map Projection required to compute LatLng of screen x/y');
+ assert(zoom != null,
+ 'Current map zoom level required to compute LatLng of screen x/y');
- final scale = 1 << map.zoom; // 2 ^ zoom
+ final ne = bounds!.northEast;
+ final sw = bounds.southWest;
+
+ final topRight = projection!.fromLatLngToPoint!(ne)!;
+ final bottomLeft = projection.fromLatLngToPoint!(sw)!;
+
+ final scale = 1 << (zoom!.toInt()); // 2 ^ zoom
final point =
- gmaps.Point((x / scale) + bottomLeft.x, (y / scale) + topRight.y);
+ gmaps.Point((x / scale) + bottomLeft.x!, (y / scale) + topRight.y!);
- return projection.fromPointToLatLng(point);
+ return projection.fromPointToLatLng!(point)!;
}
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 cc8d79a..2528490 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
@@ -27,11 +27,11 @@
String _getViewType(int mapId) => 'plugins.flutter.io/google_maps_$mapId';
// The Flutter widget that contains the rendered Map.
- HtmlElementView _widget;
- HtmlElement _div;
+ HtmlElementView? _widget;
+ late HtmlElement _div;
/// The Flutter widget that will contain the rendered Map. Used for caching.
- HtmlElementView get widget {
+ Widget? get widget {
if (_widget == null && !_streamController.isClosed) {
_widget = HtmlElementView(
viewType: _getViewType(_mapId),
@@ -41,14 +41,14 @@
}
// The currently-enabled traffic layer.
- gmaps.TrafficLayer _trafficLayer;
+ gmaps.TrafficLayer? _trafficLayer;
/// A getter for the current traffic layer. Only for tests.
@visibleForTesting
- gmaps.TrafficLayer get trafficLayer => _trafficLayer;
+ gmaps.TrafficLayer? get trafficLayer => _trafficLayer;
// The underlying GMap instance. This is the interface with the JS SDK.
- gmaps.GMap _googleMap;
+ gmaps.GMap? _googleMap;
// The StreamController used by this controller and the geometry ones.
final StreamController<MapEvent> _streamController;
@@ -57,10 +57,10 @@
Stream<MapEvent> get events => _streamController.stream;
// Geometry controllers, for different features of the map.
- CirclesController _circlesController;
- PolygonsController _polygonsController;
- PolylinesController _polylinesController;
- MarkersController _markersController;
+ CirclesController? _circlesController;
+ PolygonsController? _polygonsController;
+ PolylinesController? _polylinesController;
+ MarkersController? _markersController;
// Keeps track if _attachGeometryControllers has been called or not.
bool _controllersBoundToMap = false;
@@ -69,9 +69,9 @@
/// Initializes the GMap, and the sub-controllers related to it. Wires events.
GoogleMapController({
- @required int mapId,
- @required StreamController<MapEvent> streamController,
- @required CameraPosition initialCameraPosition,
+ required int mapId,
+ required StreamController<MapEvent> streamController,
+ required CameraPosition initialCameraPosition,
Set<Marker> markers = const <Marker>{},
Set<Polygon> polygons = const <Polygon>{},
Set<Polyline> polylines = const <Polyline>{},
@@ -107,11 +107,11 @@
/// Overrides certain properties to install mocks defined during testing.
@visibleForTesting
void debugSetOverrides({
- DebugCreateMapFunction createMap,
- MarkersController markers,
- CirclesController circles,
- PolygonsController polygons,
- PolylinesController polylines,
+ DebugCreateMapFunction? createMap,
+ MarkersController? markers,
+ CirclesController? circles,
+ PolygonsController? polygons,
+ PolylinesController? polylines,
}) {
_overrideCreateMap = createMap;
_markersController = markers ?? _markersController;
@@ -120,11 +120,11 @@
_polylinesController = polylines ?? _polylinesController;
}
- DebugCreateMapFunction _overrideCreateMap;
+ DebugCreateMapFunction? _overrideCreateMap;
gmaps.GMap _createMap(HtmlElement div, gmaps.MapOptions options) {
if (_overrideCreateMap != null) {
- return _overrideCreateMap(div, options);
+ return _overrideCreateMap!(div, options);
}
return gmaps.GMap(div, options);
}
@@ -142,10 +142,11 @@
options = _applyInitialPosition(_initialCameraPosition, options);
// Create the map...
- _googleMap = _createMap(_div, options);
+ final map = _createMap(_div, options);
+ _googleMap = map;
- _attachMapEvents(_googleMap);
- _attachGeometryControllers(_googleMap);
+ _attachMapEvents(map);
+ _attachGeometryControllers(map);
_renderInitialGeometry(
markers: _markers,
@@ -154,19 +155,21 @@
polylines: _polylines,
);
- _setTrafficLayer(_googleMap, _isTrafficLayerEnabled(_rawMapOptions));
+ _setTrafficLayer(map, _isTrafficLayerEnabled(_rawMapOptions));
}
// Funnels map gmap events into the plugin's stream controller.
void _attachMapEvents(gmaps.GMap map) {
map.onClick.listen((event) {
+ assert(event.latLng != null);
_streamController.add(
- MapTapEvent(_mapId, _gmLatLngToLatLng(event.latLng)),
+ MapTapEvent(_mapId, _gmLatLngToLatLng(event.latLng!)),
);
});
map.onRightclick.listen((event) {
+ assert(event.latLng != null);
_streamController.add(
- MapLongPressEvent(_mapId, _gmLatLngToLatLng(event.latLng)),
+ MapLongPressEvent(_mapId, _gmLatLngToLatLng(event.latLng!)),
);
});
map.onBoundsChanged.listen((event) {
@@ -188,28 +191,47 @@
void _attachGeometryControllers(gmaps.GMap map) {
// Now we can add the initial geometry.
// And bind the (ready) map instance to the other geometry controllers.
- _circlesController.bindToMap(_mapId, map);
- _polygonsController.bindToMap(_mapId, map);
- _polylinesController.bindToMap(_mapId, map);
- _markersController.bindToMap(_mapId, map);
+ //
+ // These controllers are either created in the constructor of this class, or
+ // overriden (for testing) by the [debugSetOverrides] method. They can't be
+ // null.
+ assert(_circlesController != null,
+ 'Cannot attach a map to a null CirclesController instance.');
+ assert(_polygonsController != null,
+ 'Cannot attach a map to a null PolygonsController instance.');
+ assert(_polylinesController != null,
+ 'Cannot attach a map to a null PolylinesController instance.');
+ assert(_markersController != null,
+ 'Cannot attach a map to a null MarkersController instance.');
+
+ _circlesController!.bindToMap(_mapId, map);
+ _polygonsController!.bindToMap(_mapId, map);
+ _polylinesController!.bindToMap(_mapId, map);
+ _markersController!.bindToMap(_mapId, map);
+
_controllersBoundToMap = true;
}
// Renders the initial sets of geometry.
void _renderInitialGeometry({
- Set<Marker> markers,
- Set<Circle> circles,
- Set<Polygon> polygons,
- Set<Polyline> polylines,
+ Set<Marker> markers = const {},
+ Set<Circle> circles = const {},
+ Set<Polygon> polygons = const {},
+ Set<Polyline> polylines = const {},
}) {
assert(
_controllersBoundToMap,
'Geometry controllers must be bound to a map before any geometry can ' +
'be added to them. Ensure _attachGeometryControllers is called first.');
- _markersController.addMarkers(markers);
- _circlesController.addCircles(circles);
- _polygonsController.addPolygons(polygons);
- _polylinesController.addPolylines(polylines);
+
+ // The above assert will only succeed if the controllers have been bound to a map
+ // 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);
}
// Merges new options coming from the plugin into the _rawMapOptions map.
@@ -227,10 +249,12 @@
///
/// This method converts the map into the proper [gmaps.MapOptions]
void updateRawOptions(Map<String, dynamic> optionsUpdate) {
+ assert(_googleMap != null, 'Cannot update options on a null map.');
+
final newOptions = _mergeRawOptions(optionsUpdate);
_setOptions(_rawOptionsToGmapsOptions(newOptions));
- _setTrafficLayer(_googleMap, _isTrafficLayerEnabled(newOptions));
+ _setTrafficLayer(_googleMap!, _isTrafficLayerEnabled(newOptions));
}
// Sets new [gmaps.MapOptions] on the wrapped map.
@@ -241,11 +265,10 @@
// Attaches/detaches a Traffic Layer on the passed `map` if `attach` is true/false.
void _setTrafficLayer(gmaps.GMap map, bool attach) {
if (attach && _trafficLayer == null) {
- _trafficLayer = gmaps.TrafficLayer();
- _trafficLayer.set('map', map);
+ _trafficLayer = gmaps.TrafficLayer()..set('map', map);
}
if (!attach && _trafficLayer != null) {
- _trafficLayer.set('map', null);
+ _trafficLayer!.set('map', null);
_trafficLayer = null;
}
}
@@ -255,35 +278,61 @@
/// Returns the [LatLngBounds] of the current viewport.
Future<LatLngBounds> getVisibleRegion() async {
- return _gmLatLngBoundsTolatLngBounds(await _googleMap.bounds);
+ assert(_googleMap != null, 'Cannot get the visible region of a null map.');
+
+ return _gmLatLngBoundsTolatLngBounds(
+ await _googleMap!.bounds ?? _nullGmapsLatLngBounds,
+ );
}
/// Returns the [ScreenCoordinate] for a given viewport [LatLng].
Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) async {
+ assert(_googleMap != null,
+ 'Cannot get the screen coordinates with a null map.');
+ assert(_googleMap!.projection != null,
+ 'Cannot compute screen coordinate with a null map or projection.');
+
final point =
- _googleMap.projection.fromLatLngToPoint(_latLngToGmLatLng(latLng));
- return ScreenCoordinate(x: point.x, y: point.y);
+ _googleMap!.projection!.fromLatLngToPoint!(_latLngToGmLatLng(latLng))!;
+
+ assert(point.x != null && point.y != null,
+ 'The x and y of a ScreenCoordinate cannot be null.');
+
+ return ScreenCoordinate(x: point.x!.toInt(), y: point.y!.toInt());
}
/// Returns the [LatLng] for a `screenCoordinate` (in pixels) of the viewport.
Future<LatLng> getLatLng(ScreenCoordinate screenCoordinate) async {
+ assert(_googleMap != null,
+ 'Cannot get the lat, lng of a screen coordinate with a null map.');
+
final gmaps.LatLng latLng =
- _pixelToLatLng(_googleMap, screenCoordinate.x, screenCoordinate.y);
+ _pixelToLatLng(_googleMap!, screenCoordinate.x, screenCoordinate.y);
return _gmLatLngToLatLng(latLng);
}
/// Applies a `cameraUpdate` to the current viewport.
Future<void> moveCamera(CameraUpdate cameraUpdate) async {
- return _applyCameraUpdate(_googleMap, cameraUpdate);
+ assert(_googleMap != null, 'Cannot update the camera of a null map.');
+
+ return _applyCameraUpdate(_googleMap!, cameraUpdate);
}
/// Returns the zoom level of the current viewport.
- Future<double> getZoomLevel() async => _googleMap.zoom.toDouble();
+ Future<double> getZoomLevel() async {
+ assert(_googleMap != null, 'Cannot get zoom level of a null map.');
+ assert(_googleMap!.zoom != null,
+ 'Zoom level should not be null. Is the map correctly initialized?');
+
+ return _googleMap!.zoom!.toDouble();
+ }
// Geometry manipulation
/// Applies [CircleUpdates] to the currently managed circles.
void updateCircles(CircleUpdates updates) {
+ assert(
+ _circlesController != null, 'Cannot update circles after dispose().');
_circlesController?.addCircles(updates.circlesToAdd);
_circlesController?.changeCircles(updates.circlesToChange);
_circlesController?.removeCircles(updates.circleIdsToRemove);
@@ -291,6 +340,8 @@
/// Applies [PolygonUpdates] to the currently managed polygons.
void updatePolygons(PolygonUpdates updates) {
+ assert(
+ _polygonsController != null, 'Cannot update polygons after dispose().');
_polygonsController?.addPolygons(updates.polygonsToAdd);
_polygonsController?.changePolygons(updates.polygonsToChange);
_polygonsController?.removePolygons(updates.polygonIdsToRemove);
@@ -298,6 +349,8 @@
/// Applies [PolylineUpdates] to the currently managed lines.
void updatePolylines(PolylineUpdates updates) {
+ assert(_polylinesController != null,
+ 'Cannot update polylines after dispose().');
_polylinesController?.addPolylines(updates.polylinesToAdd);
_polylinesController?.changePolylines(updates.polylinesToChange);
_polylinesController?.removePolylines(updates.polylineIdsToRemove);
@@ -305,6 +358,8 @@
/// Applies [MarkerUpdates] to the currently managed markers.
void updateMarkers(MarkerUpdates updates) {
+ assert(
+ _markersController != null, 'Cannot update markers after dispose().');
_markersController?.addMarkers(updates.markersToAdd);
_markersController?.changeMarkers(updates.markersToChange);
_markersController?.removeMarkers(updates.markerIdsToRemove);
@@ -312,22 +367,29 @@
/// Shows the [InfoWindow] of the marker identified by its [MarkerId].
void showInfoWindow(MarkerId markerId) {
+ assert(_markersController != null,
+ 'Cannot show infowindow of marker [${markerId.value}] after dispose().');
_markersController?.showMarkerInfoWindow(markerId);
}
/// Hides the [InfoWindow] of the marker identified by its [MarkerId].
void hideInfoWindow(MarkerId markerId) {
+ assert(_markersController != null,
+ 'Cannot hide infowindow of marker [${markerId.value}] after dispose().');
_markersController?.hideMarkerInfoWindow(markerId);
}
/// Returns true if the [InfoWindow] of the marker identified by [MarkerId] is shown.
bool isInfoWindowShown(MarkerId markerId) {
- return _markersController?.isInfoWindowShown(markerId);
+ return _markersController?.isInfoWindowShown(markerId) ?? false;
}
// Cleanup
/// Disposes of this controller and its resources.
+ ///
+ /// You won't be able to call many of the methods on this controller after
+ /// calling `dispose`!
void dispose() {
_widget = null;
_googleMap = null;
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 5e95a53..692917f 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
@@ -45,7 +45,7 @@
@override
Future<void> updateMapOptions(
Map<String, dynamic> optionsUpdate, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).updateRawOptions(optionsUpdate);
}
@@ -54,7 +54,7 @@
@override
Future<void> updateMarkers(
MarkerUpdates markerUpdates, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).updateMarkers(markerUpdates);
}
@@ -63,7 +63,7 @@
@override
Future<void> updatePolygons(
PolygonUpdates polygonUpdates, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).updatePolygons(polygonUpdates);
}
@@ -72,7 +72,7 @@
@override
Future<void> updatePolylines(
PolylineUpdates polylineUpdates, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).updatePolylines(polylineUpdates);
}
@@ -81,15 +81,15 @@
@override
Future<void> updateCircles(
CircleUpdates circleUpdates, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).updateCircles(circleUpdates);
}
@override
Future<void> updateTileOverlays({
- @required Set<TileOverlay> newTileOverlays,
- @required int mapId,
+ required Set<TileOverlay> newTileOverlays,
+ required int mapId,
}) async {
return; // Noop for now!
}
@@ -97,7 +97,7 @@
@override
Future<void> clearTileCache(
TileOverlayId tileOverlayId, {
- @required int mapId,
+ required int mapId,
}) async {
return; // Noop for now!
}
@@ -106,7 +106,7 @@
@override
Future<void> animateCamera(
CameraUpdate cameraUpdate, {
- @required int mapId,
+ required int mapId,
}) async {
return moveCamera(cameraUpdate, mapId: mapId);
}
@@ -115,7 +115,7 @@
@override
Future<void> moveCamera(
CameraUpdate cameraUpdate, {
- @required int mapId,
+ required int mapId,
}) async {
return _map(mapId).moveCamera(cameraUpdate);
}
@@ -128,8 +128,8 @@
/// pass full styles.
@override
Future<void> setMapStyle(
- String mapStyle, {
- @required int mapId,
+ String? mapStyle, {
+ required int mapId,
}) async {
_map(mapId).updateRawOptions({
'styles': _mapStyles(mapStyle),
@@ -139,7 +139,7 @@
/// Returns the bounds of the current viewport.
@override
Future<LatLngBounds> getVisibleRegion({
- @required int mapId,
+ required int mapId,
}) {
return _map(mapId).getVisibleRegion();
}
@@ -148,7 +148,7 @@
@override
Future<ScreenCoordinate> getScreenCoordinate(
LatLng latLng, {
- @required int mapId,
+ required int mapId,
}) {
return _map(mapId).getScreenCoordinate(latLng);
}
@@ -157,7 +157,7 @@
@override
Future<LatLng> getLatLng(
ScreenCoordinate screenCoordinate, {
- @required int mapId,
+ required int mapId,
}) {
return _map(mapId).getLatLng(screenCoordinate);
}
@@ -170,7 +170,7 @@
@override
Future<void> showMarkerInfoWindow(
MarkerId markerId, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).showInfoWindow(markerId);
}
@@ -183,7 +183,7 @@
@override
Future<void> hideMarkerInfoWindow(
MarkerId markerId, {
- @required int mapId,
+ required int mapId,
}) async {
_map(mapId).hideInfoWindow(markerId);
}
@@ -196,7 +196,7 @@
@override
Future<bool> isMarkerInfoWindowShown(
MarkerId markerId, {
- @required int mapId,
+ required int mapId,
}) async {
return _map(mapId).isInfoWindowShown(markerId);
}
@@ -204,7 +204,7 @@
/// Returns the zoom level of the `mapId`.
@override
Future<double> getZoomLevel({
- @required int mapId,
+ required int mapId,
}) {
return _map(mapId).getZoomLevel();
}
@@ -213,64 +213,64 @@
// into the plugin
@override
- Stream<CameraMoveStartedEvent> onCameraMoveStarted({@required int mapId}) {
+ Stream<CameraMoveStartedEvent> onCameraMoveStarted({required int mapId}) {
return _events(mapId).whereType<CameraMoveStartedEvent>();
}
@override
- Stream<CameraMoveEvent> onCameraMove({@required int mapId}) {
+ Stream<CameraMoveEvent> onCameraMove({required int mapId}) {
return _events(mapId).whereType<CameraMoveEvent>();
}
@override
- Stream<CameraIdleEvent> onCameraIdle({@required int mapId}) {
+ Stream<CameraIdleEvent> onCameraIdle({required int mapId}) {
return _events(mapId).whereType<CameraIdleEvent>();
}
@override
- Stream<MarkerTapEvent> onMarkerTap({@required int mapId}) {
+ Stream<MarkerTapEvent> onMarkerTap({required int mapId}) {
return _events(mapId).whereType<MarkerTapEvent>();
}
@override
- Stream<InfoWindowTapEvent> onInfoWindowTap({@required int mapId}) {
+ Stream<InfoWindowTapEvent> onInfoWindowTap({required int mapId}) {
return _events(mapId).whereType<InfoWindowTapEvent>();
}
@override
- Stream<MarkerDragEndEvent> onMarkerDragEnd({@required int mapId}) {
+ Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) {
return _events(mapId).whereType<MarkerDragEndEvent>();
}
@override
- Stream<PolylineTapEvent> onPolylineTap({@required int mapId}) {
+ Stream<PolylineTapEvent> onPolylineTap({required int mapId}) {
return _events(mapId).whereType<PolylineTapEvent>();
}
@override
- Stream<PolygonTapEvent> onPolygonTap({@required int mapId}) {
+ Stream<PolygonTapEvent> onPolygonTap({required int mapId}) {
return _events(mapId).whereType<PolygonTapEvent>();
}
@override
- Stream<CircleTapEvent> onCircleTap({@required int mapId}) {
+ Stream<CircleTapEvent> onCircleTap({required int mapId}) {
return _events(mapId).whereType<CircleTapEvent>();
}
@override
- Stream<MapTapEvent> onTap({@required int mapId}) {
+ Stream<MapTapEvent> onTap({required int mapId}) {
return _events(mapId).whereType<MapTapEvent>();
}
@override
- Stream<MapLongPressEvent> onLongPress({@required int mapId}) {
+ Stream<MapLongPressEvent> onLongPress({required int mapId}) {
return _events(mapId).whereType<MapLongPressEvent>();
}
/// Disposes of the current map. It can't be used afterwards!
@override
- void dispose({@required int mapId}) {
- _map(mapId)?.dispose();
+ void dispose({required int mapId}) {
+ _map(mapId).dispose();
_mapById.remove(mapId);
}
@@ -278,19 +278,16 @@
Widget buildView(
int creationId,
PlatformViewCreatedCallback onPlatformViewCreated, {
- @required CameraPosition initialCameraPosition,
+ required CameraPosition initialCameraPosition,
Set<Marker> markers = const <Marker>{},
Set<Polygon> polygons = const <Polygon>{},
Set<Polyline> polylines = const <Polyline>{},
Set<Circle> circles = const <Circle>{},
Set<TileOverlay> tileOverlays = const <TileOverlay>{},
- Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers =
+ Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers =
const <Factory<OneSequenceGestureRecognizer>>{},
Map<String, dynamic> mapOptions = const <String, dynamic>{},
}) {
- assert(creationId != null,
- 'buildView needs a `_webOnlyMapCreationId` in its creationParams to prevent widget reloads in web.');
-
// Bail fast if we've already rendered this map ID...
if (_mapById[creationId]?.widget != null) {
return _mapById[creationId].widget;
@@ -314,6 +311,9 @@
onPlatformViewCreated.call(creationId);
- return mapController.widget;
+ assert(mapController.widget != null,
+ 'The widget of a GoogleMapController cannot be null before calling dispose on it.');
+
+ return mapController.widget!;
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart
index 62238fc..5b0169b 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart
@@ -6,33 +6,35 @@
/// The `MarkerController` class wraps a [gmaps.Marker], how it handles events, and its associated (optional) [gmaps.InfoWindow] widget.
class MarkerController {
- gmaps.Marker _marker;
+ gmaps.Marker? _marker;
final bool _consumeTapEvents;
- final gmaps.InfoWindow _infoWindow;
+ final gmaps.InfoWindow? _infoWindow;
bool _infoWindowShown = false;
/// Creates a `MarkerController`, which wraps a [gmaps.Marker] object, its `onTap`/`onDrag` behavior, and its associated [gmaps.InfoWindow].
MarkerController({
- @required gmaps.Marker marker,
- gmaps.InfoWindow infoWindow,
+ required gmaps.Marker marker,
+ gmaps.InfoWindow? infoWindow,
bool consumeTapEvents = false,
- LatLngCallback onDragEnd,
- ui.VoidCallback onTap,
+ LatLngCallback? onDragEnd,
+ ui.VoidCallback? onTap,
}) : _marker = marker,
_infoWindow = infoWindow,
_consumeTapEvents = consumeTapEvents {
if (onTap != null) {
- _marker.onClick.listen((event) {
+ marker.onClick.listen((event) {
onTap.call();
});
}
if (onDragEnd != null) {
- _marker.onDragend.listen((event) {
- _marker.position = event.latLng;
- onDragEnd.call(event.latLng);
+ marker.onDragend.listen((event) {
+ if (marker != null) {
+ marker.position = event.latLng;
+ }
+ onDragEnd.call(event.latLng ?? _nullGmapsLatLng);
});
}
}
@@ -44,42 +46,54 @@
bool get infoWindowShown => _infoWindowShown;
/// Returns the [gmaps.Marker] associated to this controller.
- gmaps.Marker get marker => _marker;
+ gmaps.Marker? get marker => _marker;
/// Returns the [gmaps.InfoWindow] associated to the marker.
@visibleForTesting
- gmaps.InfoWindow get infoWindow => _infoWindow;
+ gmaps.InfoWindow? get infoWindow => _infoWindow;
/// Updates the options of the wrapped [gmaps.Marker] object.
+ ///
+ /// This cannot be called after [remove].
void update(
gmaps.MarkerOptions options, {
- String newInfoWindowContent,
+ HtmlElement? newInfoWindowContent,
}) {
- _marker.options = options;
+ assert(_marker != null, 'Cannot `update` Marker after calling `remove`.');
+ _marker!.options = options;
if (_infoWindow != null && newInfoWindowContent != null) {
- _infoWindow.content = newInfoWindowContent;
+ _infoWindow!.content = newInfoWindowContent;
}
}
/// Disposes of the currently wrapped [gmaps.Marker].
void remove() {
- _marker.visible = false;
- _marker.map = null;
- _marker = null;
+ if (_marker != null) {
+ _infoWindowShown = false;
+ _marker!.visible = false;
+ _marker!.map = null;
+ _marker = null;
+ }
}
/// Hide the associated [gmaps.InfoWindow].
+ ///
+ /// This cannot be called after [remove].
void hideInfoWindow() {
+ assert(_marker != null, 'Cannot `hideInfoWindow` on a `remove`d Marker.');
if (_infoWindow != null) {
- _infoWindow.close();
+ _infoWindow!.close();
_infoWindowShown = false;
}
}
/// Show the associated [gmaps.InfoWindow].
+ ///
+ /// This cannot be called after [remove].
void showInfoWindow() {
+ assert(_marker != null, 'Cannot `showInfoWindow` on a `remove`d Marker.');
if (_infoWindow != null) {
- _infoWindow.open(_marker.map, _marker);
+ _infoWindow!.open(_marker!.map, _marker);
_infoWindowShown = true;
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
index bc9827a..704577b 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart
@@ -14,8 +14,8 @@
/// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
MarkersController({
- @required StreamController<MapEvent> stream,
- }) : _streamController = stream,
+ required StreamController<MapEvent> stream,
+ }) : _streamController = stream,
_markerIdToController = Map<MarkerId, MarkerController>();
/// Returns the cache of [MarkerController]s. Test only.
@@ -26,7 +26,7 @@
///
/// Wraps each [Marker] into its corresponding [MarkerController].
void addMarkers(Set<Marker> markersToAdd) {
- markersToAdd?.forEach(_addMarker);
+ markersToAdd.forEach(_addMarker);
}
void _addMarker(Marker marker) {
@@ -35,14 +35,15 @@
}
final infoWindowOptions = _infoWindowOptionsFromMarker(marker);
- gmaps.InfoWindow gmInfoWindow;
+ gmaps.InfoWindow? gmInfoWindow;
if (infoWindowOptions != null) {
gmInfoWindow = gmaps.InfoWindow(infoWindowOptions);
// Google Maps' JS SDK does not have a click event on the InfoWindow, so
// we make one...
if (infoWindowOptions.content is HtmlElement) {
- infoWindowOptions.content.onClick.listen((_) {
+ final content = infoWindowOptions.content as HtmlElement;
+ content.onClick.listen((_) {
_onInfoWindowTap(marker.markerId);
});
}
@@ -70,11 +71,11 @@
/// Updates a set of [Marker] objects with new options.
void changeMarkers(Set<Marker> markersToChange) {
- markersToChange?.forEach(_changeMarker);
+ markersToChange.forEach(_changeMarker);
}
void _changeMarker(Marker marker) {
- MarkerController markerController = _markerIdToController[marker?.markerId];
+ MarkerController? markerController = _markerIdToController[marker.markerId];
if (markerController != null) {
final markerOptions = _markerOptionsFromMarker(
marker,
@@ -83,18 +84,18 @@
final infoWindow = _infoWindowOptionsFromMarker(marker);
markerController.update(
markerOptions,
- newInfoWindowContent: infoWindow?.content,
+ newInfoWindowContent: infoWindow?.content as HtmlElement?,
);
}
}
/// Removes a set of [MarkerId]s from the cache.
void removeMarkers(Set<MarkerId> markerIdsToRemove) {
- markerIdsToRemove?.forEach(_removeMarker);
+ markerIdsToRemove.forEach(_removeMarker);
}
void _removeMarker(MarkerId markerId) {
- final MarkerController markerController = _markerIdToController[markerId];
+ final MarkerController? markerController = _markerIdToController[markerId];
markerController?.remove();
_markerIdToController.remove(markerId);
}
@@ -106,7 +107,7 @@
/// See also [hideMarkerInfoWindow] and [isInfoWindowShown].
void showMarkerInfoWindow(MarkerId markerId) {
_hideAllMarkerInfoWindow();
- MarkerController markerController = _markerIdToController[markerId];
+ MarkerController? markerController = _markerIdToController[markerId];
markerController?.showInfoWindow();
}
@@ -114,7 +115,7 @@
///
/// See also [showMarkerInfoWindow] and [isInfoWindowShown].
void hideMarkerInfoWindow(MarkerId markerId) {
- MarkerController markerController = _markerIdToController[markerId];
+ MarkerController? markerController = _markerIdToController[markerId];
markerController?.hideInfoWindow();
}
@@ -122,7 +123,7 @@
///
/// See also [showMarkerInfoWindow] and [hideMarkerInfoWindow].
bool isInfoWindowShown(MarkerId markerId) {
- MarkerController markerController = _markerIdToController[markerId];
+ MarkerController? markerController = _markerIdToController[markerId];
return markerController?.infoWindowShown ?? false;
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygon.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygon.dart
index 4ce1f02..9921d2f 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygon.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygon.dart
@@ -6,15 +6,15 @@
/// The `PolygonController` class wraps a [gmaps.Polygon] and its `onTap` behavior.
class PolygonController {
- gmaps.Polygon _polygon;
+ gmaps.Polygon? _polygon;
final bool _consumeTapEvents;
/// Creates a `PolygonController` that wraps a [gmaps.Polygon] object and its `onTap` behavior.
PolygonController({
- @required gmaps.Polygon polygon,
+ required gmaps.Polygon polygon,
bool consumeTapEvents = false,
- ui.VoidCallback onTap,
+ ui.VoidCallback? onTap,
}) : _polygon = polygon,
_consumeTapEvents = consumeTapEvents {
if (onTap != null) {
@@ -26,20 +26,25 @@
/// Returns the wrapped [gmaps.Polygon]. Only used for testing.
@visibleForTesting
- gmaps.Polygon get polygon => _polygon;
+ gmaps.Polygon? get polygon => _polygon;
/// Returns `true` if this Controller will use its own `onTap` handler to consume events.
bool get consumeTapEvents => _consumeTapEvents;
/// Updates the options of the wrapped [gmaps.Polygon] object.
+ ///
+ /// This cannot be called after [remove].
void update(gmaps.PolygonOptions options) {
- _polygon.options = options;
+ assert(_polygon != null, 'Cannot `update` Polygon after calling `remove`.');
+ _polygon!.options = options;
}
/// Disposes of the currently wrapped [gmaps.Polygon].
void remove() {
- _polygon.visible = false;
- _polygon.map = null;
- _polygon = null;
+ if (_polygon != null) {
+ _polygon!.visible = false;
+ _polygon!.map = null;
+ _polygon = null;
+ }
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygons.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygons.dart
index 4671e6d..ef51bd6 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygons.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygons.dart
@@ -14,8 +14,8 @@
/// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
PolygonsController({
- @required StreamController<MapEvent> stream,
- }) : _streamController = stream,
+ required StreamController<MapEvent> stream,
+ }) : _streamController = stream,
_polygonIdToController = Map<PolygonId, PolygonController>();
/// Returns the cache of [PolygonController]s. Test only.
@@ -60,15 +60,15 @@
}
void _changePolygon(Polygon polygon) {
- PolygonController polygonController =
- _polygonIdToController[polygon?.polygonId];
+ PolygonController? polygonController =
+ _polygonIdToController[polygon.polygonId];
polygonController?.update(_polygonOptionsFromPolygon(googleMap, polygon));
}
/// Removes a set of [PolygonId]s from the cache.
void removePolygons(Set<PolygonId> polygonIdsToRemove) {
- polygonIdsToRemove?.forEach((polygonId) {
- final PolygonController polygonController =
+ polygonIdsToRemove.forEach((polygonId) {
+ final PolygonController? polygonController =
_polygonIdToController[polygonId];
polygonController?.remove();
_polygonIdToController.remove(polygonId);
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polyline.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polyline.dart
index bf1dbb2..eb4b6d8 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polyline.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polyline.dart
@@ -6,15 +6,15 @@
/// The `PolygonController` class wraps a [gmaps.Polyline] and its `onTap` behavior.
class PolylineController {
- gmaps.Polyline _polyline;
+ gmaps.Polyline? _polyline;
final bool _consumeTapEvents;
/// Creates a `PolylineController` that wraps a [gmaps.Polyline] object and its `onTap` behavior.
PolylineController({
- @required gmaps.Polyline polyline,
+ required gmaps.Polyline polyline,
bool consumeTapEvents = false,
- ui.VoidCallback onTap,
+ ui.VoidCallback? onTap,
}) : _polyline = polyline,
_consumeTapEvents = consumeTapEvents {
if (onTap != null) {
@@ -26,20 +26,26 @@
/// Returns the wrapped [gmaps.Polyline]. Only used for testing.
@visibleForTesting
- gmaps.Polyline get line => _polyline;
+ gmaps.Polyline? get line => _polyline;
/// Returns `true` if this Controller will use its own `onTap` handler to consume events.
bool get consumeTapEvents => _consumeTapEvents;
/// Updates the options of the wrapped [gmaps.Polyline] object.
+ ///
+ /// This cannot be called after [remove].
void update(gmaps.PolylineOptions options) {
- _polyline.options = options;
+ assert(
+ _polyline != null, 'Cannot `update` Polyline after calling `remove`.');
+ _polyline!.options = options;
}
/// Disposes of the currently wrapped [gmaps.Polyline].
void remove() {
- _polyline.visible = false;
- _polyline.map = null;
- _polyline = null;
+ if (_polyline != null) {
+ _polyline!.visible = false;
+ _polyline!.map = null;
+ _polyline = null;
+ }
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polylines.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polylines.dart
index e91b82f..184c0d9 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polylines.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/polylines.dart
@@ -14,8 +14,8 @@
/// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
PolylinesController({
- @required StreamController<MapEvent> stream,
- }) : _streamController = stream,
+ required StreamController<MapEvent> stream,
+ }) : _streamController = stream,
_polylineIdToController = Map<PolylineId, PolylineController>();
/// Returns the cache of [PolylineContrller]s. Test only.
@@ -26,7 +26,7 @@
///
/// Wraps each line into its corresponding [PolylineController].
void addPolylines(Set<Polyline> polylinesToAdd) {
- polylinesToAdd?.forEach((polyline) {
+ polylinesToAdd.forEach((polyline) {
_addPolyline(polyline);
});
}
@@ -50,22 +50,22 @@
/// Updates a set of [Polyline] objects with new options.
void changePolylines(Set<Polyline> polylinesToChange) {
- polylinesToChange?.forEach((polylineToChange) {
+ polylinesToChange.forEach((polylineToChange) {
_changePolyline(polylineToChange);
});
}
void _changePolyline(Polyline polyline) {
- PolylineController polylineController =
- _polylineIdToController[polyline?.polylineId];
+ PolylineController? polylineController =
+ _polylineIdToController[polyline.polylineId];
polylineController
?.update(_polylineOptionsFromPolyline(googleMap, polyline));
}
/// Removes a set of [PolylineId]s from the cache.
void removePolylines(Set<PolylineId> polylineIdsToRemove) {
- polylineIdsToRemove?.forEach((polylineId) {
- final PolylineController polylineController =
+ polylineIdsToRemove.forEach((polylineId) {
+ final PolylineController? polylineController =
_polylineIdToController[polylineId];
polylineController?.remove();
_polylineIdToController.remove(polylineId);
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/types.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/types.dart
index 10b5199..ff980eb 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/types.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/types.dart
@@ -17,10 +17,10 @@
/// instance and our internal `mapId` value.
abstract class GeometryController {
/// The GMap instance that this controller operates on.
- gmaps.GMap googleMap;
+ late gmaps.GMap googleMap;
/// The map ID for events.
- int mapId;
+ late int mapId;
/// Binds a `mapId` and the [gmaps.GMap] instance to this controller.
void bindToMap(int mapId, gmaps.GMap googleMap) {
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 22df1b2..5312a56 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
@@ -1,7 +1,7 @@
name: google_maps_flutter_web
description: Web platform implementation of google_maps_flutter
homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
-version: 0.2.1
+version: 0.3.0
flutter:
plugin:
@@ -17,7 +17,7 @@
sdk: flutter
meta: ^1.3.0
google_maps_flutter_platform_interface: ^2.0.1
- google_maps: ^3.4.5
+ google_maps: ^5.1.0
stream_transform: ^2.0.0
sanitize_html: ^2.0.0
@@ -27,5 +27,5 @@
pedantic: ^1.10.0
environment:
- sdk: ">=2.3.0 <3.0.0"
+ sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0"
diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh
index 56b0585..06566f0 100755
--- a/script/build_all_plugins_app.sh
+++ b/script/build_all_plugins_app.sh
@@ -59,7 +59,7 @@
for version in "${BUILD_MODES[@]}"; do
echo "Building $version..."
- (cd $REPO_DIR/all_plugins && flutter build $@ --$version --no-sound-null-safety)
+ (cd $REPO_DIR/all_plugins && flutter build $@ --$version)
if [ $? -eq 0 ]; then
echo "Successfully built $version all_plugins app."