[google_maps_flutter] Fix handling of non-nullable invokeMethod return types (#3754)

During the null-safety migration I accepted the auto-migrator use of as Future<T> to handle invokeMethod<T> returning a T?. I didn't realize that as does not actually do that kind of casting, and will fail with "type 'Future<T?>' is not a subtype of type 'Future<T>' in type cast".

There were no tests that exercised these methods in any way, so automated tests didn't catch the bug. This adds a minimal test that calls all of the non-void methods to ensure that they don't explode (and a TODO to backfill full unit tests of the entire method channel).

Fixes flutter/flutter#78426
Fixes flutter/flutter#78856
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
index e8b12f7..1a62083 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.3
+
+* Fix type issues in `isMarkerInfoWindowShown` and `getZoomLevel` introduced
+  in the null safety migration.
+
 ## 2.0.2
 
 * Mark constructors for CameraUpdate, CircleId, MapsObjectId, MarkerId, PolygonId, PolylineId and TileOverlayId as const
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
index 80a71b4..49029cc 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
@@ -62,8 +62,9 @@
   // Keep a collection of mapId to a map of TileOverlays.
   final Map<int, Map<TileOverlayId, TileOverlay>> _tileOverlays = {};
 
-  @override
-  Future<void> init(int mapId) {
+  /// Returns the channel for [mapId], creating it if it doesn't already exist.
+  @visibleForTesting
+  MethodChannel ensureChannelInitialized(int mapId) {
     MethodChannel? channel = _channels[mapId];
     if (channel == null) {
       channel = MethodChannel('plugins.flutter.io/google_maps_$mapId');
@@ -71,6 +72,12 @@
           (MethodCall call) => _handleMethodCall(call, mapId));
       _channels[mapId] = channel;
     }
+    return channel;
+  }
+
+  @override
+  Future<void> init(int mapId) {
+    MethodChannel channel = ensureChannelInitialized(mapId);
     return channel.invokeMethod<void>('map#waitForMap');
   }
 
@@ -414,18 +421,17 @@
   Future<bool> isMarkerInfoWindowShown(
     MarkerId markerId, {
     required int mapId,
-  }) {
+  }) async {
     assert(markerId != null);
-    return channel(mapId).invokeMethod<bool>('markers#isInfoWindowShown',
-        <String, String>{'markerId': markerId.value}) as Future<bool>;
+    return (await channel(mapId).invokeMethod<bool>('markers#isInfoWindowShown',
+        <String, String>{'markerId': markerId.value}))!;
   }
 
   @override
   Future<double> getZoomLevel({
     required int mapId,
-  }) {
-    return channel(mapId).invokeMethod<double>('map#getZoomLevel')
-        as Future<double>;
+  }) async {
+    return (await channel(mapId).invokeMethod<double>('map#getZoomLevel'))!;
   }
 
   @override
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
index 9d41935..6280991 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.0.2
+version: 2.0.3
 
 dependencies:
   flutter:
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart
new file mode 100644
index 0000000..19e81c9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart
@@ -0,0 +1,72 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$MethodChannelGoogleMapsFlutter', () {
+    late List<String> log;
+
+    setUp(() async {
+      log = <String>[];
+    });
+
+    /// Initializes a map with the given ID and canned responses, logging all
+    /// calls to [log].
+    void configureMockMap(
+      MethodChannelGoogleMapsFlutter maps, {
+      required int mapId,
+      required Future<dynamic>? Function(MethodCall call) handler,
+    }) {
+      maps
+          .ensureChannelInitialized(mapId)
+          .setMockMethodCallHandler((MethodCall methodCall) {
+        log.add(methodCall.method);
+        return handler(methodCall);
+      });
+    }
+
+    // Calls each method that uses invokeMethod with a return type other than
+    // void to ensure that the casting/nullability handling succeeds.
+    //
+    // TODO(stuartmorgan): Remove this once there is real test coverage of
+    // each method, since that would cover this issue.
+    test('non-void invokeMethods handle types correctly', () async {
+      const int mapId = 0;
+      final MethodChannelGoogleMapsFlutter maps =
+          MethodChannelGoogleMapsFlutter();
+      configureMockMap(maps, mapId: mapId,
+          handler: (MethodCall methodCall) async {
+        switch (methodCall.method) {
+          case 'map#getLatLng':
+            return <dynamic>[1.0, 2.0];
+          case 'markers#isInfoWindowShown':
+            return true;
+          case 'map#getZoomLevel':
+            return 2.5;
+          case 'map#takeSnapshot':
+            return null;
+        }
+      });
+
+      await maps.getLatLng(ScreenCoordinate(x: 0, y: 0), mapId: mapId);
+      await maps.isMarkerInfoWindowShown(MarkerId(''), mapId: mapId);
+      await maps.getZoomLevel(mapId: mapId);
+      await maps.takeSnapshot(mapId: mapId);
+      // Check that all the invokeMethod calls happened.
+      expect(log, <String>[
+        'map#getLatLng',
+        'markers#isInfoWindowShown',
+        'map#getZoomLevel',
+        'map#takeSnapshot',
+      ]);
+    });
+  });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart
index 73cea10..2c50313 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import 'package:mockito/mockito.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:plugin_platform_interface/plugin_platform_interface.dart';
 
@@ -36,26 +35,6 @@
       GoogleMapsFlutterPlatform.instance = ExtendsGoogleMapsFlutterPlatform();
     });
   });
-
-  group('$MethodChannelGoogleMapsFlutter', () {
-    const MethodChannel channel =
-        MethodChannel('plugins.flutter.io/google_maps_flutter');
-    final List<MethodCall> log = <MethodCall>[];
-    channel.setMockMethodCallHandler((MethodCall methodCall) async {
-      log.add(methodCall);
-    });
-
-    tearDown(() {
-      log.clear();
-    });
-
-    test('foo', () async {
-      expect(
-        log,
-        <Matcher>[],
-      );
-    });
-  });
 }
 
 class GoogleMapsFlutterPlatformMock extends Mock