Skip to content

Commit ae09ac1

Browse files
committed
[google_maps_flutter_web] Add Advanced Markers support
1 parent b30b9e6 commit ae09ac1

22 files changed

+1824
-310
lines changed

packages/google_maps_flutter/google_maps_flutter_web/README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Modify the `<head>` tag of your `web/index.html` to load the Google Maps JavaScr
3232
The Google Maps Web SDK splits some of its functionality in [separate libraries](https://developers.google.com/maps/documentation/javascript/libraries#libraries-for-dynamic-library-import).
3333

3434
If your app needs the `drawing` library (to draw polygons, rectangles, polylines,
35-
circles or markers on a map), include it like this:
35+
circles or legacy markers on a map), include it like this:
3636

3737
```html
3838
<script
@@ -44,12 +44,26 @@ To request multiple libraries, separate them with commas:
4444

4545
```html
4646
<script
47-
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=drawing,visualization,places">
47+
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=drawing,marker,visualization,places">
4848
</script>
4949
```
5050

5151
Now you should be able to use the Google Maps plugin normally.
5252

53+
## Advanced Markers
54+
55+
The Google Maps SDK provides Advanced Markers, which replace the older legacy markers. Advanced Markers offer improved performance, richer customization (including scalable pins, custom HTML-like content, and styling options), and better behavior on vector maps such as collision management and altitude control. Legacy Marker APIs are deprecated, and new features will only be available through the Advanced Marker system.
56+
57+
If your app uses Advanced Markers, include `marker` library like this:
58+
```html
59+
<script
60+
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=marker">
61+
</script>
62+
```
63+
64+
For full details, see Google's official documentation:
65+
https://developers.google.com/maps/documentation/javascript/advanced-markers/overview
66+
5367
## Marker clustering
5468

5569
If you need marker clustering support, modify the <head> tag to load the [js-markerclusterer](https://github.com/googlemaps/js-markerclusterer#install) library. Ensure you are using the currently supported version `2.5.3`, like so:
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:js_interop';
7+
8+
import 'package:flutter_test/flutter_test.dart';
9+
import 'package:google_maps/google_maps.dart' as gmaps;
10+
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
11+
import 'package:google_maps_flutter_web/src/utils.dart';
12+
import 'package:integration_test/integration_test.dart';
13+
14+
/// Test Markers
15+
void main() {
16+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
17+
18+
// Since onTap/DragEnd events happen asynchronously, we need to store when the event
19+
// is fired. We use a completer so the test can wait for the future to be completed.
20+
late Completer<bool> methodCalledCompleter;
21+
22+
/// This is the future value of the [methodCalledCompleter]. Reinitialized
23+
/// in the [setUp] method, and completed (as `true`) by [onTap] and [onDragEnd]
24+
/// when those methods are called from the MarkerController.
25+
late Future<bool> methodCalled;
26+
27+
void onTap() {
28+
methodCalledCompleter.complete(true);
29+
}
30+
31+
void onDragStart(gmaps.LatLng _) {
32+
methodCalledCompleter.complete(true);
33+
}
34+
35+
void onDrag(gmaps.LatLng _) {
36+
methodCalledCompleter.complete(true);
37+
}
38+
39+
void onDragEnd(gmaps.LatLng _) {
40+
methodCalledCompleter.complete(true);
41+
}
42+
43+
setUp(() {
44+
methodCalledCompleter = Completer<bool>();
45+
methodCalled = methodCalledCompleter.future;
46+
});
47+
48+
group('MarkerController', () {
49+
late gmaps.AdvancedMarkerElement marker;
50+
51+
setUp(() {
52+
marker = gmaps.AdvancedMarkerElement();
53+
});
54+
55+
testWidgets('onTap gets called', (WidgetTester tester) async {
56+
AdvancedMarkerController(marker: marker, onTap: onTap);
57+
58+
// Trigger a click event...
59+
gmaps.event.trigger(marker, 'click', gmaps.MapMouseEvent());
60+
61+
// The event handling is now truly async. Wait for it...
62+
expect(await methodCalled, isTrue);
63+
});
64+
65+
testWidgets('onDragStart gets called', (WidgetTester tester) async {
66+
AdvancedMarkerController(marker: marker, onDragStart: onDragStart);
67+
68+
// Trigger a drag end event...
69+
gmaps.event.trigger(
70+
marker,
71+
'dragstart',
72+
gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
73+
);
74+
75+
expect(await methodCalled, isTrue);
76+
});
77+
78+
testWidgets('onDrag gets called', (WidgetTester tester) async {
79+
AdvancedMarkerController(marker: marker, onDrag: onDrag);
80+
81+
// Trigger a drag end event...
82+
gmaps.event.trigger(
83+
marker,
84+
'drag',
85+
gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
86+
);
87+
88+
expect(await methodCalled, isTrue);
89+
});
90+
91+
testWidgets('onDragEnd gets called', (WidgetTester tester) async {
92+
AdvancedMarkerController(marker: marker, onDragEnd: onDragEnd);
93+
94+
// Trigger a drag end event...
95+
gmaps.event.trigger(
96+
marker,
97+
'dragend',
98+
gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
99+
);
100+
101+
expect(await methodCalled, isTrue);
102+
});
103+
104+
testWidgets('update', (WidgetTester tester) async {
105+
final controller = AdvancedMarkerController(marker: marker);
106+
final options = gmaps.AdvancedMarkerElementOptions()
107+
..collisionBehavior =
108+
gmaps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY
109+
..gmpDraggable = true
110+
..position = gmaps.LatLng(42, 54);
111+
112+
expect(marker.collisionBehavior, gmaps.CollisionBehavior.REQUIRED);
113+
expect(marker.gmpDraggable, isFalse);
114+
115+
controller.update(options);
116+
117+
expect(marker.gmpDraggable, isTrue);
118+
expect(
119+
marker.collisionBehavior,
120+
gmaps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY,
121+
);
122+
final JSAny? position = marker.position;
123+
expect(position, isNotNull);
124+
expect(position is gmaps.LatLngLiteral, isTrue);
125+
expect((position! as gmaps.LatLngLiteral).lat, equals(42));
126+
expect((position as gmaps.LatLngLiteral).lng, equals(54));
127+
});
128+
129+
testWidgets('infoWindow null, showInfoWindow.', (
130+
WidgetTester tester,
131+
) async {
132+
final controller = AdvancedMarkerController(marker: marker);
133+
134+
controller.showInfoWindow();
135+
136+
expect(controller.infoWindowShown, isFalse);
137+
});
138+
139+
testWidgets('showInfoWindow', (WidgetTester tester) async {
140+
final infoWindow = gmaps.InfoWindow();
141+
final map = gmaps.Map(createDivElement());
142+
marker.map = map;
143+
final controller = AdvancedMarkerController(
144+
marker: marker,
145+
infoWindow: infoWindow,
146+
);
147+
148+
controller.showInfoWindow();
149+
150+
expect(infoWindow.get('map'), map);
151+
expect(controller.infoWindowShown, isTrue);
152+
});
153+
154+
testWidgets('hideInfoWindow', (WidgetTester tester) async {
155+
final infoWindow = gmaps.InfoWindow();
156+
final map = gmaps.Map(createDivElement());
157+
marker.map = map;
158+
final controller = AdvancedMarkerController(
159+
marker: marker,
160+
infoWindow: infoWindow,
161+
);
162+
163+
controller.hideInfoWindow();
164+
165+
expect(infoWindow.get('map'), isNull);
166+
expect(controller.infoWindowShown, isFalse);
167+
});
168+
169+
group('remove', () {
170+
late AdvancedMarkerController controller;
171+
172+
setUp(() {
173+
final infoWindow = gmaps.InfoWindow();
174+
final map = gmaps.Map(createDivElement());
175+
marker.map = map;
176+
controller = AdvancedMarkerController(
177+
marker: marker,
178+
infoWindow: infoWindow,
179+
);
180+
});
181+
182+
testWidgets('drops gmaps instance', (WidgetTester tester) async {
183+
controller.remove();
184+
185+
expect(controller.marker, isNull);
186+
});
187+
188+
testWidgets('cannot call update after remove', (
189+
WidgetTester tester,
190+
) async {
191+
final options = gmaps.AdvancedMarkerElementOptions()
192+
..gmpDraggable = true;
193+
194+
controller.remove();
195+
196+
expect(() {
197+
controller.update(options);
198+
}, throwsAssertionError);
199+
});
200+
201+
testWidgets('cannot call showInfoWindow after remove', (
202+
WidgetTester tester,
203+
) async {
204+
controller.remove();
205+
206+
expect(() {
207+
controller.showInfoWindow();
208+
}, throwsStateError);
209+
});
210+
211+
testWidgets('cannot call hideInfoWindow after remove', (
212+
WidgetTester tester,
213+
) async {
214+
controller.remove();
215+
216+
expect(() {
217+
controller.hideInfoWindow();
218+
}, throwsAssertionError);
219+
});
220+
});
221+
});
222+
}

0 commit comments

Comments
 (0)