Skip to content

Commit e18dc4d

Browse files
ditmanfotiDim
authored andcommitted
[google_maps_flutter_web] Fix getScreenCoordinate, zIndex of Circles (flutter#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).
1 parent b48425c commit e18dc4d

17 files changed

+636
-132
lines changed

packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.1
2+
3+
* Fix the `getScreenCoordinate(LatLng)` method. [#80710](https://github.com/flutter/flutter/issues/80710)
4+
* Wait until the map tiles have loaded before calling `onPlatformViewCreated`, so
5+
the returned controller is 100% functional (has bounds, a projection, etc...)
6+
* Use zIndex property when initializing Circle objects. [#89374](https://github.com/flutter/flutter/issues/89374)
7+
18
## 0.3.0+4
29

310
* Add `implements` to pubspec.

packages/google_maps_flutter/google_maps_flutter_web/LICENSE

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
google_maps_flutter_web
2+
13
Copyright 2013 The Flutter Authors. All rights reserved.
24

35
Redistribution and use in source and binary forms, with or without modification,
@@ -23,3 +25,27 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
2325
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2426
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
2527
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
--------------------------------------------------------------------------------
29+
to_screen_location
30+
31+
The MIT License (MIT)
32+
33+
Copyright (c) 2008 Krasimir Tsonev
34+
35+
Permission is hereby granted, free of charge, to any person obtaining a copy
36+
of this software and associated documentation files (the "Software"), to deal
37+
in the Software without restriction, including without limitation the rights
38+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39+
copies of the Software, and to permit persons to whom the Software is
40+
furnished to do so, subject to the following conditions:
41+
42+
The above copyright notice and this permission notice shall be included in all
43+
copies or substantial portions of the Software.
44+
45+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51+
SOFTWARE.

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,19 @@ void main() {
257257
});
258258

259259
testWidgets('renders initial geometry', (WidgetTester tester) async {
260-
controller = _createController(circles: <Circle>{
261-
Circle(circleId: CircleId('circle-1'))
262-
}, markers: <Marker>{
260+
controller = _createController(circles: {
261+
Circle(
262+
circleId: CircleId('circle-1'),
263+
zIndex: 1234,
264+
),
265+
}, markers: {
263266
Marker(
264-
markerId: MarkerId('marker-1'),
265-
infoWindow: InfoWindow(
266-
title: 'title for test', snippet: 'snippet for test'))
267+
markerId: MarkerId('marker-1'),
268+
infoWindow: InfoWindow(
269+
title: 'title for test',
270+
snippet: 'snippet for test',
271+
),
272+
),
267273
}, polygons: {
268274
Polygon(polygonId: PolygonId('polygon-1'), points: [
269275
LatLng(43.355114, -5.851333),
@@ -315,6 +321,7 @@ void main() {
315321
.captured[0] as Set<Polyline>;
316322

317323
expect(capturedCircles.first.circleId.value, 'circle-1');
324+
expect(capturedCircles.first.zIndex, 1234);
318325
expect(capturedMarkers.first.markerId.value, 'marker-1');
319326
expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test');
320327
expect(capturedMarkers.first.infoWindow.title, 'title for test');

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// Mocks generated by Mockito 5.0.2 from annotations
5+
// Mocks generated by Mockito 5.0.15 from annotations
66
// in google_maps_flutter_web_integration_tests/integration_test/google_maps_controller_test.dart.
77
// Do not manually edit this file.
88

9-
import 'package:google_maps/src/generated/google_maps_core.js.g.dart' as _i2;
10-
import 'package:google_maps_flutter_platform_interface/src/types/circle.dart'
9+
import 'package:google_maps/google_maps.dart' as _i2;
10+
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'
1111
as _i4;
12-
import 'package:google_maps_flutter_platform_interface/src/types/marker.dart'
13-
as _i7;
14-
import 'package:google_maps_flutter_platform_interface/src/types/polygon.dart'
15-
as _i5;
16-
import 'package:google_maps_flutter_platform_interface/src/types/polyline.dart'
17-
as _i6;
1812
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i3;
1913
import 'package:mockito/mockito.dart' as _i1;
2014

15+
// ignore_for_file: avoid_redundant_argument_values
16+
// ignore_for_file: avoid_setters_without_getters
2117
// ignore_for_file: comment_references
18+
// ignore_for_file: implementation_imports
19+
// ignore_for_file: invalid_use_of_visible_for_testing_member
20+
// ignore_for_file: prefer_const_constructors
2221
// ignore_for_file: unnecessary_parenthesis
2322

24-
class _FakeGMap extends _i1.Fake implements _i2.GMap {}
23+
class _FakeGMap_0 extends _i1.Fake implements _i2.GMap {}
2524

2625
/// A class which mocks [CirclesController].
2726
///
@@ -34,7 +33,7 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
3433
as Map<_i4.CircleId, _i3.CircleController>);
3534
@override
3635
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
37-
returnValue: _FakeGMap()) as _i2.GMap);
36+
returnValue: _FakeGMap_0()) as _i2.GMap);
3837
@override
3938
set googleMap(_i2.GMap? _googleMap) =>
4039
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
@@ -62,6 +61,8 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
6261
void bindToMap(int? mapId, _i2.GMap? googleMap) =>
6362
super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
6463
returnValueForMissingStub: null);
64+
@override
65+
String toString() => super.toString();
6566
}
6667

6768
/// A class which mocks [PolygonsController].
@@ -70,13 +71,13 @@ class MockCirclesController extends _i1.Mock implements _i3.CirclesController {
7071
class MockPolygonsController extends _i1.Mock
7172
implements _i3.PolygonsController {
7273
@override
73-
Map<_i5.PolygonId, _i3.PolygonController> get polygons =>
74+
Map<_i4.PolygonId, _i3.PolygonController> get polygons =>
7475
(super.noSuchMethod(Invocation.getter(#polygons),
75-
returnValue: <_i5.PolygonId, _i3.PolygonController>{})
76-
as Map<_i5.PolygonId, _i3.PolygonController>);
76+
returnValue: <_i4.PolygonId, _i3.PolygonController>{})
77+
as Map<_i4.PolygonId, _i3.PolygonController>);
7778
@override
7879
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
79-
returnValue: _FakeGMap()) as _i2.GMap);
80+
returnValue: _FakeGMap_0()) as _i2.GMap);
8081
@override
8182
set googleMap(_i2.GMap? _googleMap) =>
8283
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
@@ -89,21 +90,23 @@ class MockPolygonsController extends _i1.Mock
8990
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
9091
returnValueForMissingStub: null);
9192
@override
92-
void addPolygons(Set<_i5.Polygon>? polygonsToAdd) =>
93+
void addPolygons(Set<_i4.Polygon>? polygonsToAdd) =>
9394
super.noSuchMethod(Invocation.method(#addPolygons, [polygonsToAdd]),
9495
returnValueForMissingStub: null);
9596
@override
96-
void changePolygons(Set<_i5.Polygon>? polygonsToChange) =>
97+
void changePolygons(Set<_i4.Polygon>? polygonsToChange) =>
9798
super.noSuchMethod(Invocation.method(#changePolygons, [polygonsToChange]),
9899
returnValueForMissingStub: null);
99100
@override
100-
void removePolygons(Set<_i5.PolygonId>? polygonIdsToRemove) => super
101+
void removePolygons(Set<_i4.PolygonId>? polygonIdsToRemove) => super
101102
.noSuchMethod(Invocation.method(#removePolygons, [polygonIdsToRemove]),
102103
returnValueForMissingStub: null);
103104
@override
104105
void bindToMap(int? mapId, _i2.GMap? googleMap) =>
105106
super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
106107
returnValueForMissingStub: null);
108+
@override
109+
String toString() => super.toString();
107110
}
108111

109112
/// A class which mocks [PolylinesController].
@@ -112,13 +115,13 @@ class MockPolygonsController extends _i1.Mock
112115
class MockPolylinesController extends _i1.Mock
113116
implements _i3.PolylinesController {
114117
@override
115-
Map<_i6.PolylineId, _i3.PolylineController> get lines =>
118+
Map<_i4.PolylineId, _i3.PolylineController> get lines =>
116119
(super.noSuchMethod(Invocation.getter(#lines),
117-
returnValue: <_i6.PolylineId, _i3.PolylineController>{})
118-
as Map<_i6.PolylineId, _i3.PolylineController>);
120+
returnValue: <_i4.PolylineId, _i3.PolylineController>{})
121+
as Map<_i4.PolylineId, _i3.PolylineController>);
119122
@override
120123
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
121-
returnValue: _FakeGMap()) as _i2.GMap);
124+
returnValue: _FakeGMap_0()) as _i2.GMap);
122125
@override
123126
set googleMap(_i2.GMap? _googleMap) =>
124127
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
@@ -131,35 +134,37 @@ class MockPolylinesController extends _i1.Mock
131134
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
132135
returnValueForMissingStub: null);
133136
@override
134-
void addPolylines(Set<_i6.Polyline>? polylinesToAdd) =>
137+
void addPolylines(Set<_i4.Polyline>? polylinesToAdd) =>
135138
super.noSuchMethod(Invocation.method(#addPolylines, [polylinesToAdd]),
136139
returnValueForMissingStub: null);
137140
@override
138-
void changePolylines(Set<_i6.Polyline>? polylinesToChange) => super
141+
void changePolylines(Set<_i4.Polyline>? polylinesToChange) => super
139142
.noSuchMethod(Invocation.method(#changePolylines, [polylinesToChange]),
140143
returnValueForMissingStub: null);
141144
@override
142-
void removePolylines(Set<_i6.PolylineId>? polylineIdsToRemove) => super
145+
void removePolylines(Set<_i4.PolylineId>? polylineIdsToRemove) => super
143146
.noSuchMethod(Invocation.method(#removePolylines, [polylineIdsToRemove]),
144147
returnValueForMissingStub: null);
145148
@override
146149
void bindToMap(int? mapId, _i2.GMap? googleMap) =>
147150
super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
148151
returnValueForMissingStub: null);
152+
@override
153+
String toString() => super.toString();
149154
}
150155

151156
/// A class which mocks [MarkersController].
152157
///
153158
/// See the documentation for Mockito's code generation for more information.
154159
class MockMarkersController extends _i1.Mock implements _i3.MarkersController {
155160
@override
156-
Map<_i7.MarkerId, _i3.MarkerController> get markers =>
161+
Map<_i4.MarkerId, _i3.MarkerController> get markers =>
157162
(super.noSuchMethod(Invocation.getter(#markers),
158-
returnValue: <_i7.MarkerId, _i3.MarkerController>{})
159-
as Map<_i7.MarkerId, _i3.MarkerController>);
163+
returnValue: <_i4.MarkerId, _i3.MarkerController>{})
164+
as Map<_i4.MarkerId, _i3.MarkerController>);
160165
@override
161166
_i2.GMap get googleMap => (super.noSuchMethod(Invocation.getter(#googleMap),
162-
returnValue: _FakeGMap()) as _i2.GMap);
167+
returnValue: _FakeGMap_0()) as _i2.GMap);
163168
@override
164169
set googleMap(_i2.GMap? _googleMap) =>
165170
super.noSuchMethod(Invocation.setter(#googleMap, _googleMap),
@@ -172,31 +177,33 @@ class MockMarkersController extends _i1.Mock implements _i3.MarkersController {
172177
super.noSuchMethod(Invocation.setter(#mapId, _mapId),
173178
returnValueForMissingStub: null);
174179
@override
175-
void addMarkers(Set<_i7.Marker>? markersToAdd) =>
180+
void addMarkers(Set<_i4.Marker>? markersToAdd) =>
176181
super.noSuchMethod(Invocation.method(#addMarkers, [markersToAdd]),
177182
returnValueForMissingStub: null);
178183
@override
179-
void changeMarkers(Set<_i7.Marker>? markersToChange) =>
184+
void changeMarkers(Set<_i4.Marker>? markersToChange) =>
180185
super.noSuchMethod(Invocation.method(#changeMarkers, [markersToChange]),
181186
returnValueForMissingStub: null);
182187
@override
183-
void removeMarkers(Set<_i7.MarkerId>? markerIdsToRemove) =>
188+
void removeMarkers(Set<_i4.MarkerId>? markerIdsToRemove) =>
184189
super.noSuchMethod(Invocation.method(#removeMarkers, [markerIdsToRemove]),
185190
returnValueForMissingStub: null);
186191
@override
187-
void showMarkerInfoWindow(_i7.MarkerId? markerId) =>
192+
void showMarkerInfoWindow(_i4.MarkerId? markerId) =>
188193
super.noSuchMethod(Invocation.method(#showMarkerInfoWindow, [markerId]),
189194
returnValueForMissingStub: null);
190195
@override
191-
void hideMarkerInfoWindow(_i7.MarkerId? markerId) =>
196+
void hideMarkerInfoWindow(_i4.MarkerId? markerId) =>
192197
super.noSuchMethod(Invocation.method(#hideMarkerInfoWindow, [markerId]),
193198
returnValueForMissingStub: null);
194199
@override
195-
bool isInfoWindowShown(_i7.MarkerId? markerId) =>
200+
bool isInfoWindowShown(_i4.MarkerId? markerId) =>
196201
(super.noSuchMethod(Invocation.method(#isInfoWindowShown, [markerId]),
197202
returnValue: false) as bool);
198203
@override
199204
void bindToMap(int? mapId, _i2.GMap? googleMap) =>
200205
super.noSuchMethod(Invocation.method(#bindToMap, [mapId, googleMap]),
201206
returnValueForMissingStub: null);
207+
@override
208+
String toString() => super.toString();
202209
}

packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ void main() {
2828
group('GoogleMapsPlugin', () {
2929
late MockGoogleMapController controller;
3030
late GoogleMapsPlugin plugin;
31-
int? reportedMapId;
31+
late Completer<int> reportedMapIdCompleter;
32+
int numberOnPlatformViewCreatedCalls = 0;
3233

3334
void onPlatformViewCreated(int id) {
34-
reportedMapId = id;
35+
reportedMapIdCompleter.complete(id);
36+
numberOnPlatformViewCreatedCalls++;
3537
}
3638

3739
setUp(() {
3840
controller = MockGoogleMapController();
3941
plugin = GoogleMapsPlugin();
40-
reportedMapId = null;
42+
reportedMapIdCompleter = Completer<int>();
4143
});
4244

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

55-
testWidgets('init initializes controller', (WidgetTester tester) async {
56-
await plugin.init(0);
57-
58-
verify(controller.init());
59-
});
60-
6157
testWidgets('cannot call methods after dispose',
6258
(WidgetTester tester) async {
6359
plugin.dispose(mapId: 0);
@@ -95,17 +91,17 @@ void main() {
9591
reason:
9692
'view type should contain the mapId passed when creating the map.',
9793
);
98-
expect(
99-
reportedMapId,
100-
testMapId,
101-
reason: 'Should call onPlatformViewCreated with the mapId',
102-
);
10394
expect(cache, contains(testMapId));
10495
expect(
10596
cache[testMapId],
10697
isNotNull,
10798
reason: 'cached controller cannot be null.',
10899
);
100+
expect(
101+
cache[testMapId]!.isInitialized,
102+
isTrue,
103+
reason: 'buildView calls init on the controller',
104+
);
109105
});
110106

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

123119
expect(widget, equals(expected));
120+
});
121+
122+
testWidgets(
123+
'asynchronously reports onPlatformViewCreated the first time it happens',
124+
(WidgetTester tester) async {
125+
final Map<int, GoogleMapController> cache = {};
126+
plugin.debugSetMapById(cache);
127+
128+
plugin.buildView(
129+
testMapId,
130+
onPlatformViewCreated,
131+
initialCameraPosition: initialCameraPosition,
132+
);
133+
134+
// Simulate Google Maps JS SDK being "ready"
135+
cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId));
136+
137+
expect(
138+
cache[testMapId]!.isInitialized,
139+
isTrue,
140+
reason: 'buildView calls init on the controller',
141+
);
142+
expect(
143+
await reportedMapIdCompleter.future,
144+
testMapId,
145+
reason: 'Should call onPlatformViewCreated with the mapId',
146+
);
147+
148+
// Fire repeated event again...
149+
cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId));
124150
expect(
125-
reportedMapId,
126-
isNull,
151+
numberOnPlatformViewCreatedCalls,
152+
equals(1),
127153
reason:
128-
'onPlatformViewCreated should not be called when returning a cached controller',
154+
'Should not call onPlatformViewCreated for the same controller multiple times',
129155
);
130156
});
131157
});

0 commit comments

Comments
 (0)