Skip to content

Commit

Permalink
[google_maps_flutter_web] Fix getScreenCoordinate, zIndex of Circles (#…
Browse files Browse the repository at this point in the history
…4298)

This commit:

* uses the zIndex attribute when converting Circle geometry objects.
* ensures that the getScreenCoordinate method works as expected on the web platform.
  * adds tests that can use a fully-rendered Google Map (see projection_test.dart)
    * changes the initialization flow of the web Google Map, so the Controller is only returned to the main plugin when it's ready to work.

In order to test the getScreenCoordinate method, the Controller of a fully-rendered map must be available on the test, so we can retrieve information from an actual map instance. While working on this, it was observed that the Controller was being sent to the programmer before it was truly ready (while the map was still initializing).

Instead of littering the test with imprecise timeouts that may make these tests slower (and flakier) than needed, this PR also changes the initialization process of a GMap slightly so when its Controller is returned to the user of the plugin (onPlatformViewCreated method call), it is truly ready.

For this: 

* Controller.init is immediately called after the controller is created, 
* The plugin waits for the first onTilesloaded event coming from the JS SDK, and then 
* The Controller is sent to the user

This change happens within "private" sections of the plugin, so programmers using the plugin "normally" shouldn't notice any difference whatsoever (only that the GMap might load slightly faster, and the onPlatformViewCreated callback might be firing a few hundred milliseconds later).
  • Loading branch information
ditman committed Sep 11, 2021
1 parent a4f0e88 commit 70c314c
Show file tree
Hide file tree
Showing 17 changed files with 636 additions and 132 deletions.
@@ -1,3 +1,10 @@
## 0.3.1

* Fix the `getScreenCoordinate(LatLng)` method. [#80710](https://github.com/flutter/flutter/issues/80710)
* Wait until the map tiles have loaded before calling `onPlatformViewCreated`, so
the returned controller is 100% functional (has bounds, a projection, etc...)
* Use zIndex property when initializing Circle objects. [#89374](https://github.com/flutter/flutter/issues/89374)

## 0.3.0+4

* Add `implements` to pubspec.
Expand Down
26 changes: 26 additions & 0 deletions packages/google_maps_flutter/google_maps_flutter_web/LICENSE
@@ -1,3 +1,5 @@
google_maps_flutter_web

Copyright 2013 The Flutter Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
Expand All @@ -23,3 +25,27 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
to_screen_location

The MIT License (MIT)

Copyright (c) 2008 Krasimir Tsonev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Expand Up @@ -257,13 +257,19 @@ void main() {
});

testWidgets('renders initial geometry', (WidgetTester tester) async {
controller = _createController(circles: <Circle>{
Circle(circleId: CircleId('circle-1'))
}, markers: <Marker>{
controller = _createController(circles: {
Circle(
circleId: CircleId('circle-1'),
zIndex: 1234,
),
}, markers: {
Marker(
markerId: MarkerId('marker-1'),
infoWindow: InfoWindow(
title: 'title for test', snippet: 'snippet for test'))
markerId: MarkerId('marker-1'),
infoWindow: InfoWindow(
title: 'title for test',
snippet: 'snippet for test',
),
),
}, polygons: {
Polygon(polygonId: PolygonId('polygon-1'), points: [
LatLng(43.355114, -5.851333),
Expand Down Expand Up @@ -315,6 +321,7 @@ void main() {
.captured[0] as Set<Polyline>;

expect(capturedCircles.first.circleId.value, 'circle-1');
expect(capturedCircles.first.zIndex, 1234);
expect(capturedMarkers.first.markerId.value, 'marker-1');
expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test');
expect(capturedMarkers.first.infoWindow.title, 'title for test');
Expand Down
Expand Up @@ -2,26 +2,25 @@
// 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
// Mocks generated by Mockito 5.0.15 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'
import 'package:google_maps/google_maps.dart' as _i2;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.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: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis

class _FakeGMap extends _i1.Fake implements _i2.GMap {}
class _FakeGMap_0 extends _i1.Fake implements _i2.GMap {}

/// A class which mocks [CirclesController].
///
Expand All @@ -34,7 +33,7 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
as Map<_i4.CircleId, _i3.CircleController>);
@override
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
returnValue: _FakeGMap()) as _i2.GMap);
returnValue: _FakeGMap_0()) as _i2.GMap);
@override
set googleMap(_i2.GMap? _googleMap) =>
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
Expand Down Expand Up @@ -62,6 +61,8 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
void bindToMap(int? mapId, _i2.GMap? googleMap) =>
super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
returnValueForMissingStub: null);
@override
String toString() => super.toString();
}

/// A class which mocks [PolygonsController].
Expand All @@ -70,13 +71,13 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
class MockPolygonsController extends _i1.Mock
implements _i3.PolygonsController {
@override
Map<_i5.PolygonId, _i3.PolygonController> get polygons =>
Map<_i4.PolygonId, _i3.PolygonController> get polygons =>
(super.noSuchMethod(Invocation.getter(#polygons),
returnValue: <_i5.PolygonId, _i3.PolygonController>{})
as Map<_i5.PolygonId, _i3.PolygonController>);
returnValue: <_i4.PolygonId, _i3.PolygonController>{})
as Map<_i4.PolygonId, _i3.PolygonController>);
@override
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
returnValue: _FakeGMap()) as _i2.GMap);
returnValue: _FakeGMap_0()) as _i2.GMap);
@override
set googleMap(_i2.GMap? _googleMap) =>
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
Expand All @@ -89,21 +90,23 @@ class MockPolygonsController extends _i1.Mock
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
returnValueForMissingStub: null);
@override
void addPolygons(Set<_i5.Polygon>? polygonsToAdd) =>
void addPolygons(Set<_i4.Polygon>? polygonsToAdd) =>
super.noSuchMethod(Invocation.method(#addPolygons, [polygonsToAdd]),
returnValueForMissingStub: null);
@override
void changePolygons(Set<_i5.Polygon>? polygonsToChange) =>
void changePolygons(Set<_i4.Polygon>? polygonsToChange) =>
super.noSuchMethod(Invocation.method(#changePolygons, [polygonsToChange]),
returnValueForMissingStub: null);
@override
void removePolygons(Set<_i5.PolygonId>? polygonIdsToRemove) => super
void removePolygons(Set<_i4.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);
@override
String toString() => super.toString();
}

/// A class which mocks [PolylinesController].
Expand All @@ -112,13 +115,13 @@ class MockPolygonsController extends _i1.Mock
class MockPolylinesController extends _i1.Mock
implements _i3.PolylinesController {
@override
Map<_i6.PolylineId, _i3.PolylineController> get lines =>
Map<_i4.PolylineId, _i3.PolylineController> get lines =>
(super.noSuchMethod(Invocation.getter(#lines),
returnValue: <_i6.PolylineId, _i3.PolylineController>{})
as Map<_i6.PolylineId, _i3.PolylineController>);
returnValue: <_i4.PolylineId, _i3.PolylineController>{})
as Map<_i4.PolylineId, _i3.PolylineController>);
@override
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
returnValue: _FakeGMap()) as _i2.GMap);
returnValue: _FakeGMap_0()) as _i2.GMap);
@override
set googleMap(_i2.GMap? _googleMap) =>
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
Expand All @@ -131,35 +134,37 @@ class MockPolylinesController extends _i1.Mock
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
returnValueForMissingStub: null);
@override
void addPolylines(Set<_i6.Polyline>? polylinesToAdd) =>
void addPolylines(Set<_i4.Polyline>? polylinesToAdd) =>
super.noSuchMethod(Invocation.method(#addPolylines, [polylinesToAdd]),
returnValueForMissingStub: null);
@override
void changePolylines(Set<_i6.Polyline>? polylinesToChange) => super
void changePolylines(Set<_i4.Polyline>? polylinesToChange) => super
.noSuchMethod(Invocation.method(#changePolylines, [polylinesToChange]),
returnValueForMissingStub: null);
@override
void removePolylines(Set<_i6.PolylineId>? polylineIdsToRemove) => super
void removePolylines(Set<_i4.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);
@override
String toString() => super.toString();
}

/// 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 =>
Map<_i4.MarkerId, _i3.MarkerController> get markers =>
(super.noSuchMethod(Invocation.getter(#markers),
returnValue: <_i7.MarkerId, _i3.MarkerController>{})
as Map<_i7.MarkerId, _i3.MarkerController>);
returnValue: <_i4.MarkerId, _i3.MarkerController>{})
as Map<_i4.MarkerId, _i3.MarkerController>);
@override
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
returnValue: _FakeGMap()) as _i2.GMap);
returnValue: _FakeGMap_0()) as _i2.GMap);
@override
set googleMap(_i2.GMap? _googleMap) =>
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
Expand All @@ -172,31 +177,33 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController {
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
returnValueForMissingStub: null);
@override
void addMarkers(Set<_i7.Marker>? markersToAdd) =>
void addMarkers(Set<_i4.Marker>? markersToAdd) =>
super.noSuchMethod(Invocation.method(#addMarkers, [markersToAdd]),
returnValueForMissingStub: null);
@override
void changeMarkers(Set<_i7.Marker>? markersToChange) =>
void changeMarkers(Set<_i4.Marker>? markersToChange) =>
super.noSuchMethod(Invocation.method(#changeMarkers, [markersToChange]),
returnValueForMissingStub: null);
@override
void removeMarkers(Set<_i7.MarkerId>? markerIdsToRemove) =>
void removeMarkers(Set<_i4.MarkerId>? markerIdsToRemove) =>
super.noSuchMethod(Invocation.method(#removeMarkers, [markerIdsToRemove]),
returnValueForMissingStub: null);
@override
void showMarkerInfoWindow(_i7.MarkerId? markerId) =>
void showMarkerInfoWindow(_i4.MarkerId? markerId) =>
super.noSuchMethod(Invocation.method(#showMarkerInfoWindow, [markerId]),
returnValueForMissingStub: null);
@override
void hideMarkerInfoWindow(_i7.MarkerId? markerId) =>
void hideMarkerInfoWindow(_i4.MarkerId? markerId) =>
super.noSuchMethod(Invocation.method(#hideMarkerInfoWindow, [markerId]),
returnValueForMissingStub: null);
@override
bool isInfoWindowShown(_i7.MarkerId? markerId) =>
bool isInfoWindowShown(_i4.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);
@override
String toString() => super.toString();
}
Expand Up @@ -28,16 +28,18 @@ void main() {
group('GoogleMapsPlugin', () {
late MockGoogleMapController controller;
late GoogleMapsPlugin plugin;
int? reportedMapId;
late Completer<int> reportedMapIdCompleter;
int numberOnPlatformViewCreatedCalls = 0;

void onPlatformViewCreated(int id) {
reportedMapId = id;
reportedMapIdCompleter.complete(id);
numberOnPlatformViewCreatedCalls++;
}

setUp(() {
controller = MockGoogleMapController();
plugin = GoogleMapsPlugin();
reportedMapId = null;
reportedMapIdCompleter = Completer<int>();
});

group('init/dispose', () {
Expand All @@ -52,12 +54,6 @@ void main() {
plugin.debugSetMapById({0: controller});
});

testWidgets('init initializes controller', (WidgetTester tester) async {
await plugin.init(0);

verify(controller.init());
});

testWidgets('cannot call methods after dispose',
(WidgetTester tester) async {
plugin.dispose(mapId: 0);
Expand Down Expand Up @@ -95,17 +91,17 @@ void main() {
reason:
'view type should contain the mapId passed when creating the map.',
);
expect(
reportedMapId,
testMapId,
reason: 'Should call onPlatformViewCreated with the mapId',
);
expect(cache, contains(testMapId));
expect(
cache[testMapId],
isNotNull,
reason: 'cached controller cannot be null.',
);
expect(
cache[testMapId]!.isInitialized,
isTrue,
reason: 'buildView calls init on the controller',
);
});

testWidgets('returns cached instance if it already exists',
Expand All @@ -121,11 +117,41 @@ void main() {
);

expect(widget, equals(expected));
});

testWidgets(
'asynchronously reports onPlatformViewCreated the first time it happens',
(WidgetTester tester) async {
final Map<int, GoogleMapController> cache = {};
plugin.debugSetMapById(cache);

plugin.buildView(
testMapId,
onPlatformViewCreated,
initialCameraPosition: initialCameraPosition,
);

// Simulate Google Maps JS SDK being "ready"
cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId));

expect(
cache[testMapId]!.isInitialized,
isTrue,
reason: 'buildView calls init on the controller',
);
expect(
await reportedMapIdCompleter.future,
testMapId,
reason: 'Should call onPlatformViewCreated with the mapId',
);

// Fire repeated event again...
cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId));
expect(
reportedMapId,
isNull,
numberOnPlatformViewCreatedCalls,
equals(1),
reason:
'onPlatformViewCreated should not be called when returning a cached controller',
'Should not call onPlatformViewCreated for the same controller multiple times',
);
});
});
Expand Down

0 comments on commit 70c314c

Please sign in to comment.