[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