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 29e9287a14fb..fd56b64dbe57 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 0ec01e025570..582288a561a4 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 @@ Make sure you have updated to the latest Flutter master. * 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 000000000000..db3104bb04c6 --- /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 fd4df2ee4fd8..1d33eea4c7f3 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: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:flutter_test/flutter_test.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'; - -class _MockCirclesController extends Mock implements CirclesController {} - -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.broadcast(); - @override - Stream get onClick => onClickController.stream; +import 'google_maps_controller_test.mocks.dart'; - final onRightclickController = StreamController.broadcast(); - @override - Stream get onRightclick => onRightclickController.stream; +// This value is used when comparing long~num, like +// LatLng values. +const _acceptableDelta = 0.0000000001; - final onBoundsChangedController = StreamController.broadcast(); - @override - Stream get onBoundsChanged => onBoundsChangedController.stream; - - final onIdleController = StreamController.broadcast(); - @override - Stream get onIdle => onIdleController.stream; -} +@GenerateMocks([], customMocks: [ + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), +]) /// Test Google Map Controller void main() { @@ -46,8 +33,8 @@ void main() { group('GoogleMapController', () { final int mapId = 33930; - GoogleMapController controller; - StreamController stream; + late GoogleMapController controller; + late StreamController stream; // Creates a controller with the default mapId and stream controller, and any `options` needed. GoogleMapController _createController({ @@ -57,17 +44,18 @@ void main() { Set polygons = const {}, Set polylines = const {}, Set circles = const {}, - Map options, + Map options = const {}, }) { return GoogleMapController( - mapId: mapId, - streamController: stream, - initialCameraPosition: initialCameraPosition, - markers: markers, - polygons: polygons, - polylines: polylines, - circles: circles, - mapOptions: options ?? {}); + mapId: mapId, + streamController: stream, + initialCameraPosition: initialCameraPosition, + markers: markers, + polygons: polygons, + polylines: polylines, + circles: circles, + mapOptions: options, + ); } setUp(() { @@ -81,7 +69,9 @@ void main() { testWidgets('constructor creates widget', (WidgetTester tester) async { expect(controller.widget, isNotNull); - expect(controller.widget.viewType, endsWith('$mapId')); + expect(controller.widget, isA()); + expect((controller.widget as HtmlElementView).viewType, + endsWith('$mapId')); }); testWidgets('widget is cached when reused', (WidgetTester tester) async { @@ -90,27 +80,130 @@ void main() { 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 @@ void main() { 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()); + expect(events[1], isA()); + expect(events[2], isA()); + expect(events[3], isA()); + expect(events[4], isA()); }); testWidgets('binds geometry controllers to map\'s', @@ -227,7 +328,7 @@ void main() { 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 @@ void main() { final capturedMarkers = verify(markers.addMarkers(captureAny)).captured[0] as Set; - 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { // 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 @@ void main() { 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 @@ void main() { group('viewport getters', () { testWidgets('getVisibleRegion', (WidgetTester tester) async { - await controller.getVisibleRegion(); + final gmCenter = map.center!; + final center = + LatLng(gmCenter.lat.toDouble(), gmCenter.lng.toDouble()); + + final bounds = await controller.getVisibleRegion(); - verify(map.bounds); + 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 @@ void main() { 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 @@ void main() { }); testWidgets('updateCircles', (WidgetTester tester) async { - final mock = _MockCirclesController(); + final mock = MockCirclesController(); controller.debugSetOverrides(circles: mock); final previous = { @@ -472,7 +562,7 @@ void main() { }); testWidgets('updateMarkers', (WidgetTester tester) async { - final mock = _MockMarkersController(); + final mock = MockMarkersController(); controller.debugSetOverrides(markers: mock); final previous = { @@ -499,7 +589,7 @@ void main() { }); testWidgets('updatePolygons', (WidgetTester tester) async { - final mock = _MockPolygonsController(); + final mock = MockPolygonsController(); controller.debugSetOverrides(polygons: mock); final previous = { @@ -526,7 +616,7 @@ void main() { }); testWidgets('updatePolylines', (WidgetTester tester) async { - final mock = _MockPolylinesController(); + final mock = MockPolylinesController(); controller.debugSetOverrides(polylines: mock); final previous = { @@ -553,9 +643,10 @@ void main() { }); 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 000000000000..47933285b208 --- /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 dee37618c940..2de431a5445e 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(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 @@ void main() { 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 cache = {}; plugin.debugSetMapById(cache); - final HtmlElementView widget = plugin.buildView( + final Widget widget = plugin.buildView( testMapId, onPlatformViewCreated, initialCameraPosition: initialCameraPosition, ); + expect(widget, isA()); 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { verify(controller.showInfoWindow(markerId)); }); + testWidgets('hideMarkerInfoWindow', (WidgetTester tester) async { final markerId = MarkerId('testing-123'); @@ -287,7 +297,10 @@ void main() { 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 @@ void main() { // Verify all event streams are filtered correctly from the main one... group('Event Streams', () { int mapId = 0; - StreamController streamController; + late StreamController streamController; setUp(() { streamController = StreamController.broadcast(); when(controller.events) @@ -308,7 +321,8 @@ void main() { }); // Dispatches a few events in the global streamController, and expects *only* the passed event to be there. - void _testStreamFiltering(Stream stream, MapEvent event) async { + Future _testStreamFiltering( + Stream 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 000000000000..43150f63ef93 --- /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> get events => + (super.noSuchMethod(Invocation.getter(#events), + returnValue: Stream<_i6.MapEvent>.empty()) + as _i5.Stream<_i6.MapEvent>); + @override + void updateRawOptions(Map? 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 moveCamera(_i7.CameraUpdate? cameraUpdate) => + (super.noSuchMethod(Invocation.method(#moveCamera, [cameraUpdate]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future getZoomLevel() => + (super.noSuchMethod(Invocation.method(#getZoomLevel, []), + returnValue: Future.value(0.0)) as _i5.Future); + @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 1a85f3bb28e1..2bfa27b73a77 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(); - final onDragEndController = StreamController(); - - @override - Stream get onClick => onClickController.stream; - - @override - Stream 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 _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 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 e9e458c85685..6f2bf610f77d 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 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('MarkersController', () { - StreamController stream; - MarkersController controller; + late StreamController events; + late MarkersController controller; + late gmaps.GMap map; setUp(() { - stream = StreamController(); - controller = MarkersController(stream: stream); + events = StreamController(); + controller = MarkersController(stream: events); + map = gmaps.GMap(html.DivElement()); + controller.bindToMap(123, map); }); testWidgets('addMarkers', (WidgetTester tester) async { @@ -48,7 +51,7 @@ void main() { }; 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { }; 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 @@ void main() { 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 = getProperty( + controller.markers[MarkerId('1')]!.marker!.icon!, + 'url', + ); + + expect(blobUrl, startsWith('blob:')); - final blobUrl = controller.markers[MarkerId('1')].marker.icon.url; final response = await http.get(Uri.parse(blobUrl)); expect(response.bodyBytes, bytes, @@ -187,8 +182,8 @@ void main() { 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 @@ void main() { 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()); 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 0c351971af7c..547aaec6dc0a 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(); - @override - Stream get onClick => onClickController.stream; -} - -class _MockPolygon extends Mock implements gmaps.Polygon { - final onClickController = StreamController(); - @override - Stream get onClick => onClickController.stream; -} - -class _MockPolyline extends Mock implements gmaps.Polyline { - final onClickController = StreamController(); - @override - Stream 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 _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 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 9bb3599311d2..80b4e0823bb5 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 @@ const _acceptableDelta = 0.01; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + late gmaps.GMap map; + + setUp(() { + map = gmaps.GMap(html.DivElement()); + }); + group('CirclesController', () { - StreamController stream; - CirclesController controller; + late StreamController events; + late CirclesController controller; setUp(() { - stream = StreamController(); - controller = CirclesController(stream: stream); + events = StreamController(); + controller = CirclesController(stream: events); + controller.bindToMap(123, map); }); testWidgets('addCircles', (WidgetTester tester) async { @@ -52,7 +58,7 @@ void main() { }; 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { }); group('PolygonsController', () { - StreamController stream; - PolygonsController controller; + late StreamController events; + late PolygonsController controller; setUp(() { - stream = StreamController(); - controller = PolygonsController(stream: stream); + events = StreamController(); + controller = PolygonsController(stream: events); + controller.bindToMap(123, map); }); testWidgets('addPolygons', (WidgetTester tester) async { @@ -137,7 +144,7 @@ void main() { }; 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 stream; - PolylinesController controller; + late StreamController events; + late PolylinesController controller; setUp(() { - stream = StreamController(); - controller = PolylinesController(stream: stream); + events = StreamController(); + controller = PolylinesController(stream: events); + controller.bindToMap(123, map); }); testWidgets('addPolylines', (WidgetTester tester) async { @@ -309,7 +313,7 @@ void main() { }; 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 @@ void main() { 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 @@ void main() { 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 28955016ab40..311f05a69dc4 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 @@ dependencies: 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 000000000000..78bcdc0f9e28 --- /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 aa52974f310e..fcac5f600acb 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 f324d1c7538f..3121d189b913 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 @@ - 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 0aa563ccb889..6dc2dab572a6 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 @@ library google_maps_flutter_web; 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 84bae1b98e2e..65057d8c869e 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 @@ part of google_maps_flutter_web; /// 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 @@ class CircleController { /// 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 659d8ac823a6..2a19d87adfec 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 @@ class CirclesController extends GeometryController { /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. CirclesController({ - @required StreamController stream, - }) : _streamController = stream, + required StreamController stream, + }) : _streamController = stream, _circleIdToController = Map(); /// Returns the cache of [CircleController]s. Test only. @@ -26,7 +26,7 @@ class CirclesController extends GeometryController { /// /// Wraps each [Circle] into its corresponding [CircleController]. void addCircles(Set circlesToAdd) { - circlesToAdd?.forEach((circle) { + circlesToAdd.forEach((circle) { _addCircle(circle); }); } @@ -50,20 +50,21 @@ class CirclesController extends GeometryController { /// Updates a set of [Circle] objects with new options. void changeCircles(Set 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 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 c875bf782474..2e71c795ff0e 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 @@ bool _isTrafficLayerEnabled(Map rawOptions) { return rawOptions['trafficEnabled'] ?? false; } -// Coverts the incoming JSON object into a List of MapTypeStyler objects. -List _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 = { - '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 = { - '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 @@ bool _isJsonMapStyle(Map value) { } // Converts an incoming JSON-encoded Style info, into the correct gmaps array. -List _mapStyles(String mapStyleJson) { +List _mapStyles(String? mapStyleJson) { List 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(); + 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() + .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 @@ LatLngBounds _gmLatLngBoundsTolatLngBounds(gmaps.LatLngBounds latLngBounds) { 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 @@ CameraPosition _gmViewportToCameraPosition(gmaps.GMap map) { // 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 @@ gmaps.InfoWindowOptions _infoWindowOptionsFromMarker(Marker marker) { // 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 @@ gmaps.InfoWindowOptions _infoWindowOptionsFromMarker(Marker marker) { // 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 @@ gmaps.MarkerOptions _markerOptionsFromMarker( 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 @@ gmaps.PolygonOptions _polygonOptionsFromPolygon( final polygonDirection = _isPolygonClockwise(path); List> paths = [path]; int holeIndex = 0; - polygon.holes?.forEach((hole) { + polygon.holes.forEach((hole) { List holePath = hole.map((point) => _latLngToGmLatLng(point)).toList(); if (_isPolygonClockwise(holePath) == polygonDirection) { @@ -387,6 +316,13 @@ gmaps.PolygonOptions _polygonOptionsFromPolygon( /// 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 path) { var direction = 0.0; for (var i = 0; i < path.length; i++) { @@ -447,7 +383,7 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { 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 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { // 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 @@ void _applyCameraUpdate(gmaps.GMap map, CameraUpdate update) { // 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; + + 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 ne = bounds!.northEast; + final sw = bounds.southWest; - final topRight = projection.fromLatLngToPoint(ne); - final bottomLeft = projection.fromLatLngToPoint(sw); + final topRight = projection!.fromLatLngToPoint!(ne)!; + final bottomLeft = projection.fromLatLngToPoint!(sw)!; - final scale = 1 << map.zoom; // 2 ^ zoom + 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 cc8d79a6226d..25284909d596 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 @@ class GoogleMapController { 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 @@ class GoogleMapController { } // 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 _streamController; @@ -57,10 +57,10 @@ class GoogleMapController { Stream 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 @@ class GoogleMapController { /// Initializes the GMap, and the sub-controllers related to it. Wires events. GoogleMapController({ - @required int mapId, - @required StreamController streamController, - @required CameraPosition initialCameraPosition, + required int mapId, + required StreamController streamController, + required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, @@ -107,11 +107,11 @@ class GoogleMapController { /// 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 @@ class GoogleMapController { _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 @@ class GoogleMapController { 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 @@ class GoogleMapController { 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 @@ class GoogleMapController { 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 markers, - Set circles, - Set polygons, - Set polylines, + Set markers = const {}, + Set circles = const {}, + Set polygons = const {}, + Set 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 @@ class GoogleMapController { /// /// This method converts the map into the proper [gmaps.MapOptions] void updateRawOptions(Map 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 @@ class GoogleMapController { // 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 @@ class GoogleMapController { /// Returns the [LatLngBounds] of the current viewport. Future 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 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 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 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 getZoomLevel() async => _googleMap.zoom.toDouble(); + Future 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 @@ class GoogleMapController { /// 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 @@ class GoogleMapController { /// 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 @@ class GoogleMapController { /// 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 @@ class GoogleMapController { /// 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 5e95a538c07a..692917fef4da 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 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future updateMapOptions( Map optionsUpdate, { - @required int mapId, + required int mapId, }) async { _map(mapId).updateRawOptions(optionsUpdate); } @@ -54,7 +54,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future updateMarkers( MarkerUpdates markerUpdates, { - @required int mapId, + required int mapId, }) async { _map(mapId).updateMarkers(markerUpdates); } @@ -63,7 +63,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future updatePolygons( PolygonUpdates polygonUpdates, { - @required int mapId, + required int mapId, }) async { _map(mapId).updatePolygons(polygonUpdates); } @@ -72,7 +72,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future updatePolylines( PolylineUpdates polylineUpdates, { - @required int mapId, + required int mapId, }) async { _map(mapId).updatePolylines(polylineUpdates); } @@ -81,15 +81,15 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future updateCircles( CircleUpdates circleUpdates, { - @required int mapId, + required int mapId, }) async { _map(mapId).updateCircles(circleUpdates); } @override Future updateTileOverlays({ - @required Set newTileOverlays, - @required int mapId, + required Set newTileOverlays, + required int mapId, }) async { return; // Noop for now! } @@ -97,7 +97,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future clearTileCache( TileOverlayId tileOverlayId, { - @required int mapId, + required int mapId, }) async { return; // Noop for now! } @@ -106,7 +106,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future animateCamera( CameraUpdate cameraUpdate, { - @required int mapId, + required int mapId, }) async { return moveCamera(cameraUpdate, mapId: mapId); } @@ -115,7 +115,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future moveCamera( CameraUpdate cameraUpdate, { - @required int mapId, + required int mapId, }) async { return _map(mapId).moveCamera(cameraUpdate); } @@ -128,8 +128,8 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { /// pass full styles. @override Future setMapStyle( - String mapStyle, { - @required int mapId, + String? mapStyle, { + required int mapId, }) async { _map(mapId).updateRawOptions({ 'styles': _mapStyles(mapStyle), @@ -139,7 +139,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { /// Returns the bounds of the current viewport. @override Future getVisibleRegion({ - @required int mapId, + required int mapId, }) { return _map(mapId).getVisibleRegion(); } @@ -148,7 +148,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future getScreenCoordinate( LatLng latLng, { - @required int mapId, + required int mapId, }) { return _map(mapId).getScreenCoordinate(latLng); } @@ -157,7 +157,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future getLatLng( ScreenCoordinate screenCoordinate, { - @required int mapId, + required int mapId, }) { return _map(mapId).getLatLng(screenCoordinate); } @@ -170,7 +170,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future showMarkerInfoWindow( MarkerId markerId, { - @required int mapId, + required int mapId, }) async { _map(mapId).showInfoWindow(markerId); } @@ -183,7 +183,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future hideMarkerInfoWindow( MarkerId markerId, { - @required int mapId, + required int mapId, }) async { _map(mapId).hideInfoWindow(markerId); } @@ -196,7 +196,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { @override Future isMarkerInfoWindowShown( MarkerId markerId, { - @required int mapId, + required int mapId, }) async { return _map(mapId).isInfoWindowShown(markerId); } @@ -204,7 +204,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { /// Returns the zoom level of the `mapId`. @override Future getZoomLevel({ - @required int mapId, + required int mapId, }) { return _map(mapId).getZoomLevel(); } @@ -213,64 +213,64 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { // into the plugin @override - Stream onCameraMoveStarted({@required int mapId}) { + Stream onCameraMoveStarted({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onCameraMove({@required int mapId}) { + Stream onCameraMove({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onCameraIdle({@required int mapId}) { + Stream onCameraIdle({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onMarkerTap({@required int mapId}) { + Stream onMarkerTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onInfoWindowTap({@required int mapId}) { + Stream onInfoWindowTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onMarkerDragEnd({@required int mapId}) { + Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onPolylineTap({@required int mapId}) { + Stream onPolylineTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onPolygonTap({@required int mapId}) { + Stream onPolygonTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onCircleTap({@required int mapId}) { + Stream onCircleTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onTap({@required int mapId}) { + Stream onTap({required int mapId}) { return _events(mapId).whereType(); } @override - Stream onLongPress({@required int mapId}) { + Stream onLongPress({required int mapId}) { return _events(mapId).whereType(); } /// 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 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { - @required CameraPosition initialCameraPosition, + required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, - Set> gestureRecognizers = + Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, }) { - 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 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { 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 62238fc2d86b..5b0169b565e5 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 @@ part of google_maps_flutter_web; /// 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 @@ class MarkerController { 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 bc9827a20270..704577b6e3fb 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 @@ class MarkersController extends GeometryController { /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. MarkersController({ - @required StreamController stream, - }) : _streamController = stream, + required StreamController stream, + }) : _streamController = stream, _markerIdToController = Map(); /// Returns the cache of [MarkerController]s. Test only. @@ -26,7 +26,7 @@ class MarkersController extends GeometryController { /// /// Wraps each [Marker] into its corresponding [MarkerController]. void addMarkers(Set markersToAdd) { - markersToAdd?.forEach(_addMarker); + markersToAdd.forEach(_addMarker); } void _addMarker(Marker marker) { @@ -35,14 +35,15 @@ class MarkersController extends GeometryController { } 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 @@ class MarkersController extends GeometryController { /// Updates a set of [Marker] objects with new options. void changeMarkers(Set 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 @@ class MarkersController extends GeometryController { 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 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 @@ class MarkersController extends GeometryController { /// See also [hideMarkerInfoWindow] and [isInfoWindowShown]. void showMarkerInfoWindow(MarkerId markerId) { _hideAllMarkerInfoWindow(); - MarkerController markerController = _markerIdToController[markerId]; + MarkerController? markerController = _markerIdToController[markerId]; markerController?.showInfoWindow(); } @@ -114,7 +115,7 @@ class MarkersController extends GeometryController { /// /// See also [showMarkerInfoWindow] and [isInfoWindowShown]. void hideMarkerInfoWindow(MarkerId markerId) { - MarkerController markerController = _markerIdToController[markerId]; + MarkerController? markerController = _markerIdToController[markerId]; markerController?.hideInfoWindow(); } @@ -122,7 +123,7 @@ class MarkersController extends GeometryController { /// /// 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 4ce1f022e586..9921d2ff3876 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 @@ part of google_maps_flutter_web; /// 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 @@ class PolygonController { /// 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 4671e6d77a87..ef51bd6043df 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 @@ class PolygonsController extends GeometryController { /// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. PolygonsController({ - @required StreamController stream, - }) : _streamController = stream, + required StreamController stream, + }) : _streamController = stream, _polygonIdToController = Map(); /// Returns the cache of [PolygonController]s. Test only. @@ -60,15 +60,15 @@ class PolygonsController extends GeometryController { } 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 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 bf1dbb222555..eb4b6d88b503 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 @@ part of google_maps_flutter_web; /// 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 @@ class PolylineController { /// 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 e91b82fd1947..184c0d954744 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 @@ class PolylinesController extends GeometryController { /// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. PolylinesController({ - @required StreamController stream, - }) : _streamController = stream, + required StreamController stream, + }) : _streamController = stream, _polylineIdToController = Map(); /// Returns the cache of [PolylineContrller]s. Test only. @@ -26,7 +26,7 @@ class PolylinesController extends GeometryController { /// /// Wraps each line into its corresponding [PolylineController]. void addPolylines(Set polylinesToAdd) { - polylinesToAdd?.forEach((polyline) { + polylinesToAdd.forEach((polyline) { _addPolyline(polyline); }); } @@ -50,22 +50,22 @@ class PolylinesController extends GeometryController { /// Updates a set of [Polyline] objects with new options. void changePolylines(Set 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 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 10b5199a894e..ff980eb4c34b 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 @@ typedef LatLngCallback = void Function(gmaps.LatLng latLng); /// 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 22df1b24afe0..5312a56cd14a 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 @@ dependencies: 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 @@ dev_dependencies: 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 56b05853fdcb..06566f059a54 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -59,7 +59,7 @@ fi 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."