[google_maps_flutter] Call platform.dispose from widget. (#2909)

diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index d26663f..02b257d 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.5.30
+
+* Add a `dispose` method to the controller to let the native side know that we're done with said controller.
+* Call `controller.dispose()` from the `dispose` method of the `GoogleMap` widget.
+
 ## 0.5.29+1
 
 * (ios) Pin dependency on GoogleMaps pod to `< 3.10`, to address https://github.com/flutter/flutter/issues/63447
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 f5ee180..f47b8e5 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
@@ -258,4 +258,9 @@
   Future<Uint8List> takeSnapshot() {
     return _googleMapsFlutterPlatform.takeSnapshot(mapId: mapId);
   }
+
+  /// Disposes of the platform resources
+  void dispose() {
+    _googleMapsFlutterPlatform.dispose(mapId: mapId);
+  }
 }
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 5cf3db1..d7f0f1a 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
@@ -252,6 +252,13 @@
   }
 
   @override
+  void dispose() async {
+    super.dispose();
+    GoogleMapController controller = await _controller.future;
+    controller.dispose();
+  }
+
+  @override
   void didUpdateWidget(GoogleMap oldWidget) {
     super.didUpdateWidget(oldWidget);
     _updateOptions();
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index a33efb9..e7e3aee 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -1,13 +1,13 @@
 name: google_maps_flutter
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter
-version: 0.5.29+1
+version: 0.5.30
 
 dependencies:
   flutter:
     sdk: flutter
   flutter_plugin_android_lifecycle: ^1.0.0
-  google_maps_flutter_platform_interface: ^1.0.1
+  google_maps_flutter_platform_interface: ^1.0.4
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart
index 7861c86..5ea9a67 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart
@@ -20,7 +20,8 @@
   setUp(() {
     // Use a mock platform so we never need to hit the MethodChannel code.
     GoogleMapsFlutterPlatform.instance = platform;
-    when(platform.buildView(any, any, any)).thenReturn(Container());
+    resetMockitoState();
+    _setupMock(platform);
   });
 
   testWidgets('_webOnlyMapCreationId increments with each GoogleMap widget', (
@@ -61,4 +62,60 @@
       ),
     ]);
   });
+
+  testWidgets('Calls platform.dispose when GoogleMap is disposed of', (
+    WidgetTester tester,
+  ) async {
+    await tester.pumpWidget(GoogleMap(
+      initialCameraPosition: CameraPosition(
+        target: LatLng(43.3608, -5.8702),
+      ),
+    ));
+
+    // Now dispose of the map...
+    await tester.pumpWidget(Container());
+
+    verify(platform.dispose(mapId: anyNamed('mapId')));
+  });
+}
+
+// Some test setup classes below...
+
+class _MockStream<T> extends Mock implements Stream<T> {}
+
+typedef _CreationCallback = void Function(int);
+
+// Installs test mocks on the platform
+void _setupMock(MockGoogleMapsFlutterPlatform platform) {
+  // Used to create the view of the map...
+  when(platform.buildView(any, any, any)).thenAnswer((realInvocation) {
+    // Call the onPlatformViewCreated callback so the controller gets created.
+    _CreationCallback onPlatformViewCreatedCb =
+        realInvocation.positionalArguments[2];
+    onPlatformViewCreatedCb.call(0);
+    return Container();
+  });
+  // Used to create the Controller
+  when(platform.onCameraIdle(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<CameraIdleEvent>());
+  when(platform.onCameraMove(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<CameraMoveEvent>());
+  when(platform.onCameraMoveStarted(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<CameraMoveStartedEvent>());
+  when(platform.onCircleTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<CircleTapEvent>());
+  when(platform.onInfoWindowTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<InfoWindowTapEvent>());
+  when(platform.onLongPress(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<MapLongPressEvent>());
+  when(platform.onMarkerDragEnd(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<MarkerDragEndEvent>());
+  when(platform.onMarkerTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<MarkerTapEvent>());
+  when(platform.onPolygonTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<PolygonTapEvent>());
+  when(platform.onPolylineTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<PolylineTapEvent>());
+  when(platform.onTap(mapId: anyNamed('mapId')))
+      .thenAnswer((_) => _MockStream<MapTapEvent>());
 }