Skip to content

Commit 23a7434

Browse files
committed
[google_maps_flutter_web] Gracefully bypass HeatmapLayer when unsupported by Maps JS SDK
Google Maps JS API version 3.65 deprecated and completely removed the HeatmapLayer constructor. To resolve the resulting runtime crashes on the web platform, this change implements a dynamic, strongly-typed feature-detection helper (isHeatmapSupported()) inside dom_window_extension.dart using standard nullable JS interop extensions. The HeatmapsController is shielded to return early and log a deduplicated warning message on the console via debugPrint at most once per controller lifecycle, making heatmap operations safe no-ops in 3.65+ environments. The web integration tests (shape_test.dart and shapes_test.dart) are updated to import dom_window_extension.dart directly and dynamically skip the heatmap controller test groups when the class is not supported by the browser environment.
1 parent 69cf959 commit 23a7434

9 files changed

Lines changed: 504 additions & 139 deletions

File tree

packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## NEXT
22

3+
* Gracefully bypasses `HeatmapLayer` instantiation and operations when unsupported by the loaded Google Maps JavaScript API (version 3.65+), preventing runtime web crashes.
34
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.
45

56
## 0.6.2+1

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

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import 'dart:js_interop';
88
import 'package:flutter_test/flutter_test.dart';
99
import 'package:google_maps/google_maps.dart' as gmaps;
1010
import 'package:google_maps/google_maps_visualization.dart' as visualization;
11+
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
1112
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
1213
import 'package:integration_test/integration_test.dart';
14+
import 'package:web/web.dart' as web;
15+
16+
import 'shared_mocks.dart';
1317

1418
/// Test Shapes (Circle, Polygon, Polyline)
1519
void main() {
@@ -203,47 +207,109 @@ void main() {
203207
});
204208

205209
group('HeatmapController', () {
206-
late visualization.HeatmapLayer heatmap;
210+
group('Heatmap on Google Maps JS SDK v3.64 (Supported)', () {
211+
late visualization.HeatmapLayer heatmap;
212+
late JSObject? originalHeatmapLayer;
213+
late JSObject? visualizationNamespace;
207214

208-
setUp(() {
209-
heatmap = visualization.HeatmapLayer();
210-
});
215+
setUp(() {
216+
injectMockHeatmapLayer();
211217

212-
testWidgets('update', (WidgetTester tester) async {
213-
final controller = HeatmapController(heatmap: heatmap);
214-
final options = visualization.HeatmapLayerOptions()
215-
..data = <gmaps.LatLng>[gmaps.LatLng(0, 0)].toJS;
218+
final JSObject? google = web.window.nullableGoogle;
219+
final JSObject? maps = google!.nullableMaps;
220+
visualizationNamespace = maps!.nullableVisualization;
221+
originalHeatmapLayer = visualizationNamespace?.nullableHeatmapLayer;
216222

217-
expect(heatmap.data.array.toDart, hasLength(0));
223+
visualizationNamespace?.nullableHeatmapLayer =
224+
web.window.mockHeatmapLayer;
218225

219-
controller.update(options);
226+
heatmap = visualization.HeatmapLayer();
227+
});
228+
229+
tearDown(() {
230+
if (visualizationNamespace != null) {
231+
visualizationNamespace!.nullableHeatmapLayer = originalHeatmapLayer;
232+
}
233+
});
234+
235+
testWidgets('update', (WidgetTester tester) async {
236+
final controller = HeatmapController(heatmap: heatmap);
237+
final options = visualization.HeatmapLayerOptions()
238+
..data = <gmaps.LatLng>[gmaps.LatLng(0, 0)].toJS;
239+
240+
expect(heatmap.data.array.toDart, hasLength(0));
241+
242+
controller.update(options);
243+
244+
expect(heatmap.data.array.toDart, hasLength(1));
245+
});
246+
247+
group('remove', () {
248+
late HeatmapController controller;
249+
250+
setUp(() {
251+
controller = HeatmapController(heatmap: heatmap);
252+
});
220253

221-
expect(heatmap.data.array.toDart, hasLength(1));
254+
testWidgets('drops gmaps instance', (WidgetTester tester) async {
255+
controller.remove();
256+
257+
expect(controller.heatmap, isNull);
258+
});
259+
260+
testWidgets('cannot call update after remove', (
261+
WidgetTester tester,
262+
) async {
263+
final options = visualization.HeatmapLayerOptions()
264+
..dissipating = true;
265+
266+
controller.remove();
267+
268+
expect(() {
269+
controller.update(options);
270+
}, throwsAssertionError);
271+
});
272+
});
222273
});
223274

224-
group('remove', () {
225-
late HeatmapController controller;
275+
group('Heatmap on Google Maps JS SDK v3.65 (Unsupported)', () {
276+
late JSObject? originalHeatmapLayer;
277+
late JSObject? visualizationNamespace;
226278

227279
setUp(() {
228-
controller = HeatmapController(heatmap: heatmap);
280+
final JSObject? google = web.window.nullableGoogle;
281+
final JSObject? maps = google!.nullableMaps;
282+
visualizationNamespace = maps!.nullableVisualization;
283+
originalHeatmapLayer = visualizationNamespace?.nullableHeatmapLayer;
229284
});
230285

231-
testWidgets('drops gmaps instance', (WidgetTester tester) async {
232-
controller.remove();
233-
234-
expect(controller.heatmap, isNull);
286+
tearDown(() {
287+
if (visualizationNamespace != null) {
288+
visualizationNamespace!.nullableHeatmapLayer = originalHeatmapLayer;
289+
}
235290
});
236291

237-
testWidgets('cannot call update after remove', (
292+
testWidgets('gracefully bypasses and logs warning exactly once', (
238293
WidgetTester tester,
239294
) async {
240-
final options = visualization.HeatmapLayerOptions()..dissipating = true;
241-
242-
controller.remove();
243-
244-
expect(() {
245-
controller.update(options);
246-
}, throwsAssertionError);
295+
final controller = HeatmapsController();
296+
final map = gmaps.Map(
297+
web.document.createElement('div') as web.HTMLDivElement,
298+
);
299+
controller.bindToMap(123, map);
300+
301+
final heatmaps = <Heatmap>{
302+
const Heatmap(
303+
heatmapId: HeatmapId('1'),
304+
data: <WeightedLatLng>[],
305+
radius: HeatmapRadius.fromPixels(20),
306+
),
307+
};
308+
309+
controller.addHeatmaps(heatmaps);
310+
311+
expect(controller.heatmaps.isEmpty, isTrue);
312+
expect(controller.pendingHeatmaps.length, 1);
247313
});
248314
});
249315
});

0 commit comments

Comments
 (0)