From 79772e1dfc0d573f2e3ea732de7a5688cb13623c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Wed, 15 Jan 2025 13:45:49 +0800 Subject: [PATCH 01/10] feat: add mounted check to google map controller --- .../integration_test/src/maps_controller.dart | 31 +++ .../lib/src/controller.dart | 192 ++++++++++++++---- 2 files changed, 186 insertions(+), 37 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart index d44aa4d341e2..8090ab4cd649 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart @@ -593,6 +593,37 @@ void runTests() { final String? error = await controller.getStyleError(); expect(error, isNotNull); }); + + testWidgets('testMapStateException', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + + await pumpMap( + tester, + GoogleMap( + initialCameraPosition: kInitialCameraPosition, + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + ); + await tester.pumpAndSettle(const Duration(seconds: 3)); + final GoogleMapController controller = await controllerCompleter.future; + + final double zoomLevel = await controller.getZoomLevel(); + expect(zoomLevel > 0, true); + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(const Duration(seconds: 3)); + + try { + await controller.getZoomLevel(); + fail('expected MapStateException'); + } on MapStateException catch (e) { + expect(e.message.isNotEmpty, true); + } + }); + // skip: isAndroid || isWeb || isIOS); } /// Repeatedly checks an asynchronous value against a test condition. diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 5dd1cfdfd2ad..b2f7475fbc94 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -6,6 +6,18 @@ part of '../google_maps_flutter.dart'; +/// Exception for map state errors when calling a function +/// of the map when it's not mounted. +class MapStateException implements Exception { + /// Creates a new instance of [MapStateException]. + MapStateException(this.message); + /// Error message describing the exception. + final String message; + + @override + String toString() => 'MapStateException: $message'; +} + /// Controller for a single GoogleMap instance running on the host platform. class GoogleMapController { GoogleMapController._( @@ -91,8 +103,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateMapConfiguration(MapConfiguration update) { - return GoogleMapsFlutterPlatform.instance + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance .updateMapConfiguration(update, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateMapConfiguration on an unmounted GoogleMap instance.'); + } } /// Updates marker configuration. @@ -102,8 +119,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateMarkers(MarkerUpdates markerUpdates) { - return GoogleMapsFlutterPlatform.instance - .updateMarkers(markerUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updateMarkers(markerUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateMarkers on an unmounted GoogleMap instance.'); + } } /// Updates cluster manager configuration. @@ -114,8 +136,13 @@ class GoogleMapController { /// The returned [Future] completes after listeners have been notified. Future _updateClusterManagers( ClusterManagerUpdates clusterManagerUpdates) { - return GoogleMapsFlutterPlatform.instance - .updateClusterManagers(clusterManagerUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updateClusterManagers(clusterManagerUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateClusterManagers on an unmounted GoogleMap instance.'); + } } /// Updates polygon configuration. @@ -125,8 +152,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updatePolygons(PolygonUpdates polygonUpdates) { - return GoogleMapsFlutterPlatform.instance - .updatePolygons(polygonUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updatePolygons(polygonUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updatePolygons on an unmounted GoogleMap instance.'); + } } /// Updates polyline configuration. @@ -136,8 +168,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updatePolylines(PolylineUpdates polylineUpdates) { - return GoogleMapsFlutterPlatform.instance - .updatePolylines(polylineUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updatePolylines(polylineUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updatePolylines on an unmounted GoogleMap instance.'); + } } /// Updates circle configuration. @@ -147,8 +184,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateCircles(CircleUpdates circleUpdates) { - return GoogleMapsFlutterPlatform.instance - .updateCircles(circleUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updateCircles(circleUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateCircles on an unmounted GoogleMap instance.'); + } } /// Updates heatmap configuration. @@ -158,8 +200,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateHeatmaps(HeatmapUpdates heatmapUpdates) { - return GoogleMapsFlutterPlatform.instance - .updateHeatmaps(heatmapUpdates, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updateHeatmaps(heatmapUpdates, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateHeatmaps on an unmounted GoogleMap instance.'); + } } /// Updates tile overlays configuration. @@ -169,8 +216,13 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateTileOverlays(Set newTileOverlays) { - return GoogleMapsFlutterPlatform.instance - .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method _updateTileOverlays on an unmounted GoogleMap instance.'); + } } /// Clears the tile cache so that all tiles will be requested again from the @@ -181,8 +233,13 @@ class GoogleMapController { /// in-memory cache of tiles. If you want to cache tiles for longer, you /// should implement an on-disk cache. Future clearTileCache(TileOverlayId tileOverlayId) async { - return GoogleMapsFlutterPlatform.instance - .clearTileCache(tileOverlayId, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .clearTileCache(tileOverlayId, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method clearTileCache on an unmounted GoogleMap instance.'); + } } /// Starts an animated change of the map camera position. @@ -190,8 +247,13 @@ class GoogleMapController { /// The returned [Future] completes after the change has been started on the /// platform side. Future animateCamera(CameraUpdate cameraUpdate) { - return GoogleMapsFlutterPlatform.instance - .animateCamera(cameraUpdate, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .animateCamera(cameraUpdate, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method animateCamera on an unmounted GoogleMap instance.'); + } } /// Changes the map camera position. @@ -199,8 +261,13 @@ class GoogleMapController { /// The returned [Future] completes after the change has been made on the /// platform side. Future moveCamera(CameraUpdate cameraUpdate) { - return GoogleMapsFlutterPlatform.instance - .moveCamera(cameraUpdate, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .moveCamera(cameraUpdate, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method moveCamera on an unmounted GoogleMap instance.'); + } } /// Sets the styling of the base map. @@ -218,18 +285,33 @@ class GoogleMapController { /// style reference for more information regarding the supported styles. @Deprecated('Use GoogleMap.style instead.') Future setMapStyle(String? mapStyle) { - return GoogleMapsFlutterPlatform.instance - .setMapStyle(mapStyle, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .setMapStyle(mapStyle, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method setMapStyle on an unmounted GoogleMap instance.'); + } } /// Returns the last style error, if any. Future getStyleError() { - return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method getStyleError on an unmounted GoogleMap instance.'); + } } /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { - return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method getVisibleRegion on an unmounted GoogleMap instance.'); + } } /// Return [ScreenCoordinate] of the [LatLng] in the current map view. @@ -238,8 +320,13 @@ class GoogleMapController { /// Screen location is in screen pixels (not display pixels) with respect to the top left corner /// of the map, not necessarily of the whole screen. Future getScreenCoordinate(LatLng latLng) { - return GoogleMapsFlutterPlatform.instance - .getScreenCoordinate(latLng, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .getScreenCoordinate(latLng, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method getScreenCoordinate on an unmounted GoogleMap instance.'); + } } /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. @@ -247,8 +334,13 @@ class GoogleMapController { /// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen /// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen. Future getLatLng(ScreenCoordinate screenCoordinate) { - return GoogleMapsFlutterPlatform.instance - .getLatLng(screenCoordinate, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .getLatLng(screenCoordinate, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method getLatLng on an unmounted GoogleMap instance.'); + } } /// Programmatically show the Info Window for a [Marker]. @@ -260,8 +352,13 @@ class GoogleMapController { /// * [hideMarkerInfoWindow] to hide the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. Future showMarkerInfoWindow(MarkerId markerId) { - return GoogleMapsFlutterPlatform.instance - .showMarkerInfoWindow(markerId, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .showMarkerInfoWindow(markerId, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method showMarkerInfoWindow on an unmounted GoogleMap instance.'); + } } /// Programmatically hide the Info Window for a [Marker]. @@ -272,9 +369,15 @@ class GoogleMapController { /// * See also: /// * [showMarkerInfoWindow] to show the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. + /// Future hideMarkerInfoWindow(MarkerId markerId) { - return GoogleMapsFlutterPlatform.instance - .hideMarkerInfoWindow(markerId, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .hideMarkerInfoWindow(markerId, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method hideMarkerInfoWindow on an unmounted GoogleMap instance.'); + } } /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. @@ -286,18 +389,33 @@ class GoogleMapController { /// * [showMarkerInfoWindow] to show the Info Window. /// * [hideMarkerInfoWindow] to hide the Info Window. Future isMarkerInfoWindowShown(MarkerId markerId) { - return GoogleMapsFlutterPlatform.instance - .isMarkerInfoWindowShown(markerId, mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance + .isMarkerInfoWindowShown(markerId, mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method isMarkerInfoWindowShown on an unmounted GoogleMap instance.'); + } } /// Returns the current zoom level of the map Future getZoomLevel() { - return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method getZoomLevel on an unmounted GoogleMap instance.'); + } } /// Returns the image bytes of the map Future takeSnapshot() { - return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); + if (_googleMapState.mounted) { + return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); + } else { + throw MapStateException( + 'Cannot call method takeSnapshot on an unmounted GoogleMap instance.'); + } } /// Disposes of the platform resources From db562c7e8b582577531dc30b1c43b63454de1be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Fri, 17 Jan 2025 15:09:54 +0800 Subject: [PATCH 02/10] chore: formatting --- .../lib/src/controller.dart | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index b2f7475fbc94..140caaffcd19 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -11,6 +11,7 @@ part of '../google_maps_flutter.dart'; class MapStateException implements Exception { /// Creates a new instance of [MapStateException]. MapStateException(this.message); + /// Error message describing the exception. final String message; @@ -105,10 +106,10 @@ class GoogleMapController { Future _updateMapConfiguration(MapConfiguration update) { if (_googleMapState.mounted) { return GoogleMapsFlutterPlatform.instance - .updateMapConfiguration(update, mapId: mapId); + .updateMapConfiguration(update, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateMapConfiguration on an unmounted GoogleMap instance.'); + 'Cannot call method _updateMapConfiguration on an unmounted GoogleMap instance.'); } } @@ -124,7 +125,7 @@ class GoogleMapController { .updateMarkers(markerUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateMarkers on an unmounted GoogleMap instance.'); + 'Cannot call method _updateMarkers on an unmounted GoogleMap instance.'); } } @@ -141,7 +142,7 @@ class GoogleMapController { .updateClusterManagers(clusterManagerUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateClusterManagers on an unmounted GoogleMap instance.'); + 'Cannot call method _updateClusterManagers on an unmounted GoogleMap instance.'); } } @@ -157,7 +158,7 @@ class GoogleMapController { .updatePolygons(polygonUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updatePolygons on an unmounted GoogleMap instance.'); + 'Cannot call method _updatePolygons on an unmounted GoogleMap instance.'); } } @@ -173,7 +174,7 @@ class GoogleMapController { .updatePolylines(polylineUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updatePolylines on an unmounted GoogleMap instance.'); + 'Cannot call method _updatePolylines on an unmounted GoogleMap instance.'); } } @@ -189,7 +190,7 @@ class GoogleMapController { .updateCircles(circleUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateCircles on an unmounted GoogleMap instance.'); + 'Cannot call method _updateCircles on an unmounted GoogleMap instance.'); } } @@ -205,7 +206,7 @@ class GoogleMapController { .updateHeatmaps(heatmapUpdates, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateHeatmaps on an unmounted GoogleMap instance.'); + 'Cannot call method _updateHeatmaps on an unmounted GoogleMap instance.'); } } @@ -221,7 +222,7 @@ class GoogleMapController { .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } else { throw MapStateException( - 'Cannot call method _updateTileOverlays on an unmounted GoogleMap instance.'); + 'Cannot call method _updateTileOverlays on an unmounted GoogleMap instance.'); } } @@ -238,7 +239,7 @@ class GoogleMapController { .clearTileCache(tileOverlayId, mapId: mapId); } else { throw MapStateException( - 'Cannot call method clearTileCache on an unmounted GoogleMap instance.'); + 'Cannot call method clearTileCache on an unmounted GoogleMap instance.'); } } @@ -252,7 +253,7 @@ class GoogleMapController { .animateCamera(cameraUpdate, mapId: mapId); } else { throw MapStateException( - 'Cannot call method animateCamera on an unmounted GoogleMap instance.'); + 'Cannot call method animateCamera on an unmounted GoogleMap instance.'); } } @@ -266,7 +267,7 @@ class GoogleMapController { .moveCamera(cameraUpdate, mapId: mapId); } else { throw MapStateException( - 'Cannot call method moveCamera on an unmounted GoogleMap instance.'); + 'Cannot call method moveCamera on an unmounted GoogleMap instance.'); } } @@ -290,7 +291,7 @@ class GoogleMapController { .setMapStyle(mapStyle, mapId: mapId); } else { throw MapStateException( - 'Cannot call method setMapStyle on an unmounted GoogleMap instance.'); + 'Cannot call method setMapStyle on an unmounted GoogleMap instance.'); } } @@ -300,7 +301,7 @@ class GoogleMapController { return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); } else { throw MapStateException( - 'Cannot call method getStyleError on an unmounted GoogleMap instance.'); + 'Cannot call method getStyleError on an unmounted GoogleMap instance.'); } } @@ -310,7 +311,7 @@ class GoogleMapController { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); } else { throw MapStateException( - 'Cannot call method getVisibleRegion on an unmounted GoogleMap instance.'); + 'Cannot call method getVisibleRegion on an unmounted GoogleMap instance.'); } } @@ -325,7 +326,7 @@ class GoogleMapController { .getScreenCoordinate(latLng, mapId: mapId); } else { throw MapStateException( - 'Cannot call method getScreenCoordinate on an unmounted GoogleMap instance.'); + 'Cannot call method getScreenCoordinate on an unmounted GoogleMap instance.'); } } @@ -339,7 +340,7 @@ class GoogleMapController { .getLatLng(screenCoordinate, mapId: mapId); } else { throw MapStateException( - 'Cannot call method getLatLng on an unmounted GoogleMap instance.'); + 'Cannot call method getLatLng on an unmounted GoogleMap instance.'); } } @@ -357,7 +358,7 @@ class GoogleMapController { .showMarkerInfoWindow(markerId, mapId: mapId); } else { throw MapStateException( - 'Cannot call method showMarkerInfoWindow on an unmounted GoogleMap instance.'); + 'Cannot call method showMarkerInfoWindow on an unmounted GoogleMap instance.'); } } @@ -376,7 +377,7 @@ class GoogleMapController { .hideMarkerInfoWindow(markerId, mapId: mapId); } else { throw MapStateException( - 'Cannot call method hideMarkerInfoWindow on an unmounted GoogleMap instance.'); + 'Cannot call method hideMarkerInfoWindow on an unmounted GoogleMap instance.'); } } @@ -394,7 +395,7 @@ class GoogleMapController { .isMarkerInfoWindowShown(markerId, mapId: mapId); } else { throw MapStateException( - 'Cannot call method isMarkerInfoWindowShown on an unmounted GoogleMap instance.'); + 'Cannot call method isMarkerInfoWindowShown on an unmounted GoogleMap instance.'); } } @@ -404,7 +405,7 @@ class GoogleMapController { return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); } else { throw MapStateException( - 'Cannot call method getZoomLevel on an unmounted GoogleMap instance.'); + 'Cannot call method getZoomLevel on an unmounted GoogleMap instance.'); } } @@ -414,7 +415,7 @@ class GoogleMapController { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } else { throw MapStateException( - 'Cannot call method takeSnapshot on an unmounted GoogleMap instance.'); + 'Cannot call method takeSnapshot on an unmounted GoogleMap instance.'); } } From 83a914d4138df2c7e6fcff021ab76b9c7ec03d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Fri, 17 Jan 2025 18:02:13 +0800 Subject: [PATCH 03/10] chore: update changelog --- packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 4 ++-- packages/google_maps_flutter/google_maps_flutter/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index ef5c348d68b7..82f2a6a8d674 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,6 @@ -## NEXT +## 2.11.0 -* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Adds map mounted check to google map controller map functions. ## 2.10.0 diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 6afe27288aaf..6ae92fd7f666 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.10.0 +version: 2.11.0 environment: sdk: ^3.4.0 From bf503d4c5acc8571a47c3a81e23137a9516a68ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Tue, 21 Jan 2025 16:17:25 +0800 Subject: [PATCH 04/10] refactor: minor pr fixes --- packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 1 + .../example/integration_test/src/maps_controller.dart | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 82f2a6a8d674..1376a60f3764 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.11.0 * Adds map mounted check to google map controller map functions. +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. ## 2.10.0 diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart index 8090ab4cd649..22af4e553663 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart @@ -623,7 +623,6 @@ void runTests() { expect(e.message.isNotEmpty, true); } }); - // skip: isAndroid || isWeb || isIOS); } /// Repeatedly checks an asynchronous value against a test condition. From 3b95e6914e1ad9b79de0701b0fd206cf1c93497c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Tue, 21 Jan 2025 17:20:16 +0800 Subject: [PATCH 05/10] refactor: google map controller mounted check fixes --- .../lib/src/controller.dart | 72 +++++-------------- .../lib/src/google_map.dart | 1 - 2 files changed, 16 insertions(+), 57 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 140caaffcd19..c8171d3f9550 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -104,13 +104,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateMapConfiguration(MapConfiguration update) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateMapConfiguration(update, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateMapConfiguration on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateMapConfiguration(update, mapId: mapId); } /// Updates marker configuration. @@ -120,13 +115,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateMarkers(MarkerUpdates markerUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateMarkers(markerUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateMarkers on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateMarkers(markerUpdates, mapId: mapId); } /// Updates cluster manager configuration. @@ -137,13 +127,8 @@ class GoogleMapController { /// The returned [Future] completes after listeners have been notified. Future _updateClusterManagers( ClusterManagerUpdates clusterManagerUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateClusterManagers(clusterManagerUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateClusterManagers on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateClusterManagers(clusterManagerUpdates, mapId: mapId); } /// Updates polygon configuration. @@ -153,13 +138,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updatePolygons(PolygonUpdates polygonUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updatePolygons(polygonUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updatePolygons on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updatePolygons(polygonUpdates, mapId: mapId); } /// Updates polyline configuration. @@ -169,13 +149,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updatePolylines(PolylineUpdates polylineUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updatePolylines(polylineUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updatePolylines on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updatePolylines(polylineUpdates, mapId: mapId); } /// Updates circle configuration. @@ -185,13 +160,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateCircles(CircleUpdates circleUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateCircles(circleUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateCircles on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateCircles(circleUpdates, mapId: mapId); } /// Updates heatmap configuration. @@ -201,13 +171,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateHeatmaps(HeatmapUpdates heatmapUpdates) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateHeatmaps(heatmapUpdates, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateHeatmaps on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateHeatmaps(heatmapUpdates, mapId: mapId); } /// Updates tile overlays configuration. @@ -217,13 +182,8 @@ class GoogleMapController { /// /// The returned [Future] completes after listeners have been notified. Future _updateTileOverlays(Set newTileOverlays) { - if (_googleMapState.mounted) { - return GoogleMapsFlutterPlatform.instance - .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); - } else { - throw MapStateException( - 'Cannot call method _updateTileOverlays on an unmounted GoogleMap instance.'); - } + return GoogleMapsFlutterPlatform.instance + .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } /// Clears the tile cache so that all tiles will be requested again from the diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 888195ca36d2..64b9eee99f8a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -469,7 +469,6 @@ class _GoogleMapState extends State { this, ); _controller.complete(controller); - unawaited(_updateTileOverlays()); final MapCreatedCallback? onMapCreated = widget.onMapCreated; if (onMapCreated != null) { onMapCreated(controller); From 1ea245db8f2bb90f97a45d24e01eaebd49d6d81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Wed, 22 Jan 2025 16:24:33 +0800 Subject: [PATCH 06/10] fix: missing tile overlays init in google map --- .../google_maps_flutter/lib/src/google_map.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 64b9eee99f8a..1ce7395cea7c 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -360,6 +360,7 @@ class _GoogleMapState extends State { circles: widget.circles, clusterManagers: widget.clusterManagers, heatmaps: widget.heatmaps, + tileOverlays: widget.tileOverlays ), mapConfiguration: _mapConfiguration, ); From 0012dedd69f5d020207e5c115fa683006ee9ca82 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 23 Jan 2025 15:41:50 +0200 Subject: [PATCH 07/10] fix: properly handle initial tile overlays and tile disposal on Android and iOS platforms --- .../google_maps_flutter/example/pubspec.yaml | 14 ++++++++++++++ .../google_maps_flutter/lib/src/google_map.dart | 2 +- .../google_maps_flutter/pubspec.yaml | 12 ++++++++++++ .../lib/src/google_maps_flutter_android.dart | 7 ++++++- .../lib/src/google_maps_flutter_ios.dart | 7 ++++++- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index 0a949e262bbe..f27708fe7404 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -33,3 +33,17 @@ flutter: uses-material-design: true assets: - assets/ + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + { + google_maps_flutter_android: + { + path: ../../../../packages/google_maps_flutter/google_maps_flutter_android, + }, + google_maps_flutter_ios: + { + path: ../../../../packages/google_maps_flutter/google_maps_flutter_ios, + }, + } diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 1ce7395cea7c..6b5bfd4ccad6 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -360,7 +360,7 @@ class _GoogleMapState extends State { circles: widget.circles, clusterManagers: widget.clusterManagers, heatmaps: widget.heatmaps, - tileOverlays: widget.tileOverlays + tileOverlays: widget.tileOverlays, ), mapConfiguration: _mapConfiguration, ); diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 6ae92fd7f666..41716cf8f708 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -41,3 +41,15 @@ topics: # The example deliberately includes limited-use secrets. false_secrets: - /example/web/index.html + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + { + google_maps_flutter_android: + { + path: ../../../packages/google_maps_flutter/google_maps_flutter_android, + }, + google_maps_flutter_ios: + { path: ../../../packages/google_maps_flutter/google_maps_flutter_ios }, + } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index dd19bed86468..1950562c16a2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -133,7 +133,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { @override void dispose({required int mapId}) { - // Noop! + _tileOverlays.remove(mapId); } // The controller we need to broadcast the different events coming @@ -529,6 +529,11 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { .toList(), ); + if (!_tileOverlays.containsKey(creationId)) { + // Set initial tile overlays on new map creation. + _tileOverlays[creationId] = keyTileOverlayId(mapObjects.tileOverlays); + } + const String viewType = 'plugins.flutter.dev/google_maps_android'; if (useAndroidViewSurface) { return PlatformViewLink( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index 89c6b90ddc60..f51bea5bb223 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -119,7 +119,7 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { @override void dispose({required int mapId}) { - // Noop! + _tileOverlays.remove(mapId); } // The controller we need to broadcast the different events coming @@ -471,6 +471,11 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { .toList(), ); + if (!_tileOverlays.containsKey(creationId)) { + // Set initial tile overlays on new map creation. + _tileOverlays[creationId] = keyTileOverlayId(mapObjects.tileOverlays); + } + return UiKitView( viewType: 'plugins.flutter.dev/google_maps_ios', onPlatformViewCreated: onPlatformViewCreated, From b767ce802be39b21e217f23d81619981a7d3637a Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 23 Jan 2025 15:48:06 +0200 Subject: [PATCH 08/10] chore: minor documentation changes --- .../lib/src/google_maps_flutter_android.dart | 4 ++-- .../lib/src/google_maps_flutter_ios.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 1950562c16a2..2c12b270d3be 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -529,8 +529,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { .toList(), ); - if (!_tileOverlays.containsKey(creationId)) { - // Set initial tile overlays on new map creation. + if (_tileOverlays[creationId] == null) { + // Initialize the tile overlays for the mapId. _tileOverlays[creationId] = keyTileOverlayId(mapObjects.tileOverlays); } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index f51bea5bb223..5ce36dcc68cb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -471,8 +471,8 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { .toList(), ); - if (!_tileOverlays.containsKey(creationId)) { - // Set initial tile overlays on new map creation. + if (_tileOverlays[creationId] == null) { + // Initialize the tile overlays for the mapId. _tileOverlays[creationId] = keyTileOverlayId(mapObjects.tileOverlays); } From 5f4c4f37514a219141176af644c28e75aac8e98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Fri, 24 Jan 2025 14:57:26 +0800 Subject: [PATCH 09/10] chore: update CHANGELOG files --- .../google_maps_flutter_android/CHANGELOG.md | 4 ++++ .../google_maps_flutter_android/pubspec.yaml | 2 +- .../google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md | 3 ++- .../google_maps_flutter/google_maps_flutter_ios/pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index e18bd40228cf..7e416c8c895e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.14.13 + +* Minor fix for managing tile overlays. + ## 2.14.12 * Updates androidx.annotation:annotation to 1.9.1. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index 19db56e19133..353f8e028583 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.14.12 +version: 2.14.13 environment: sdk: ^3.5.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index b09f2f375b20..db6f5af53935 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.14.0 +* Minor fix for managing tile overlays. * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. ## 2.13.2 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index 2020ec8940ef..08b76f1cb18f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_ios description: iOS implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.13.2 +version: 2.14.0 environment: sdk: ^3.4.0 From c4310dd1bcb3676dd62ef8def9a2f8a22bc75a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4limaa?= Date: Thu, 6 Feb 2025 17:29:28 +0800 Subject: [PATCH 10/10] test: add map state exception unit test --- .../test/google_map_test.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart index 9276a7dbda30..50532e3382d8 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -579,4 +582,41 @@ void main() { expect(map.mapConfiguration.style, ''); }); + + testWidgets('testMapStateException', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: + const CameraPosition(target: LatLng(10.0, 15.0)), + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 3)); + final GoogleMapController controller = await controllerCompleter.future; + + try { + await controller.getZoomLevel(); + } on MapStateException { + fail('should not throw MapStateException'); + } + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(const Duration(seconds: 3)); + + try { + await controller.getZoomLevel(); + fail('expected MapStateException'); + } on MapStateException catch (e) { + expect(e.message.isNotEmpty, true); + } + }); }