@@ -264,6 +264,7 @@ class GoogleMapController {
264264 /// in-memory cache of tiles. If you want to cache tiles for longer, you
265265 /// should implement an on-disk cache.
266266 Future <void > clearTileCache (TileOverlayId tileOverlayId) async {
267+ _checkWidgetMountedOrThrow ();
267268 return GoogleMapsFlutterPlatform .instance.clearTileCache (
268269 tileOverlayId,
269270 mapId: mapId,
@@ -278,6 +279,7 @@ class GoogleMapController {
278279 /// The returned [Future] completes after the change has been started on the
279280 /// platform side.
280281 Future <void > animateCamera (CameraUpdate cameraUpdate, {Duration ? duration}) {
282+ _checkWidgetMountedOrThrow ();
281283 return GoogleMapsFlutterPlatform .instance.animateCameraWithConfiguration (
282284 cameraUpdate,
283285 CameraUpdateAnimationConfiguration (duration: duration),
@@ -290,6 +292,7 @@ class GoogleMapController {
290292 /// The returned [Future] completes after the change has been made on the
291293 /// platform side.
292294 Future <void > moveCamera (CameraUpdate cameraUpdate) {
295+ _checkWidgetMountedOrThrow ();
293296 return GoogleMapsFlutterPlatform .instance.moveCamera (
294297 cameraUpdate,
295298 mapId: mapId,
@@ -311,6 +314,7 @@ class GoogleMapController {
311314 /// style reference for more information regarding the supported styles.
312315 @Deprecated ('Use GoogleMap.style instead.' )
313316 Future <void > setMapStyle (String ? mapStyle) {
317+ _checkWidgetMountedOrThrow ();
314318 return GoogleMapsFlutterPlatform .instance.setMapStyle (
315319 mapStyle,
316320 mapId: mapId,
@@ -319,11 +323,13 @@ class GoogleMapController {
319323
320324 /// Returns the last style error, if any.
321325 Future <String ?> getStyleError () {
326+ _checkWidgetMountedOrThrow ();
322327 return GoogleMapsFlutterPlatform .instance.getStyleError (mapId: mapId);
323328 }
324329
325330 /// Return [LatLngBounds] defining the region that is visible in a map.
326331 Future <LatLngBounds > getVisibleRegion () {
332+ _checkWidgetMountedOrThrow ();
327333 return GoogleMapsFlutterPlatform .instance.getVisibleRegion (mapId: mapId);
328334 }
329335
@@ -333,6 +339,7 @@ class GoogleMapController {
333339 /// Screen location is in screen pixels (not display pixels) with respect to the top left corner
334340 /// of the map, not necessarily of the whole screen.
335341 Future <ScreenCoordinate > getScreenCoordinate (LatLng latLng) {
342+ _checkWidgetMountedOrThrow ();
336343 return GoogleMapsFlutterPlatform .instance.getScreenCoordinate (
337344 latLng,
338345 mapId: mapId,
@@ -344,6 +351,7 @@ class GoogleMapController {
344351 /// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen
345352 /// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen.
346353 Future <LatLng > getLatLng (ScreenCoordinate screenCoordinate) {
354+ _checkWidgetMountedOrThrow ();
347355 return GoogleMapsFlutterPlatform .instance.getLatLng (
348356 screenCoordinate,
349357 mapId: mapId,
@@ -359,6 +367,7 @@ class GoogleMapController {
359367 /// * [hideMarkerInfoWindow] to hide the Info Window.
360368 /// * [isMarkerInfoWindowShown] to check if the Info Window is showing.
361369 Future <void > showMarkerInfoWindow (MarkerId markerId) {
370+ _checkWidgetMountedOrThrow ();
362371 return GoogleMapsFlutterPlatform .instance.showMarkerInfoWindow (
363372 markerId,
364373 mapId: mapId,
@@ -374,6 +383,7 @@ class GoogleMapController {
374383 /// * [showMarkerInfoWindow] to show the Info Window.
375384 /// * [isMarkerInfoWindowShown] to check if the Info Window is showing.
376385 Future <void > hideMarkerInfoWindow (MarkerId markerId) {
386+ _checkWidgetMountedOrThrow ();
377387 return GoogleMapsFlutterPlatform .instance.hideMarkerInfoWindow (
378388 markerId,
379389 mapId: mapId,
@@ -389,6 +399,7 @@ class GoogleMapController {
389399 /// * [showMarkerInfoWindow] to show the Info Window.
390400 /// * [hideMarkerInfoWindow] to hide the Info Window.
391401 Future <bool > isMarkerInfoWindowShown (MarkerId markerId) {
402+ _checkWidgetMountedOrThrow ();
392403 return GoogleMapsFlutterPlatform .instance.isMarkerInfoWindowShown (
393404 markerId,
394405 mapId: mapId,
@@ -397,11 +408,13 @@ class GoogleMapController {
397408
398409 /// Returns the current zoom level of the map
399410 Future <double > getZoomLevel () {
411+ _checkWidgetMountedOrThrow ();
400412 return GoogleMapsFlutterPlatform .instance.getZoomLevel (mapId: mapId);
401413 }
402414
403415 /// Returns the image bytes of the map
404416 Future <Uint8List ?> takeSnapshot () {
417+ _checkWidgetMountedOrThrow ();
405418 return GoogleMapsFlutterPlatform .instance.takeSnapshot (mapId: mapId);
406419 }
407420
@@ -414,4 +427,21 @@ class GoogleMapController {
414427 _streamSubscriptions.clear ();
415428 GoogleMapsFlutterPlatform .instance.dispose (mapId: mapId);
416429 }
430+
431+ /// It is relatively easy to mistakenly call a method on the controller
432+ /// after the [GoogleMap] widget has already been disposed.
433+ /// Historically, this led to Platform-side errors such as
434+ /// `MissingPluginException` or `Unable to establish connection on channel`
435+ /// errors.
436+ ///
437+ /// To facilitate debugging, this guard function
438+ /// raises a use-after-disposed [StateError] .
439+ void _checkWidgetMountedOrThrow () {
440+ if (! _googleMapState.mounted) {
441+ throw StateError (
442+ 'GoogleMapController for map ID $mapId was used after '
443+ 'the associated GoogleMap widget had already been disposed.' ,
444+ );
445+ }
446+ }
417447}
0 commit comments