From d1c44c605467c795da010a99fa22e8abfb0404d0 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 20 Jun 2024 14:15:16 -0400 Subject: [PATCH 1/9] Convert initialization wait --- .../googlemaps/GoogleMapController.java | 26 +++--- .../flutter/plugins/googlemaps/Messages.java | 81 +++++++++++++++++++ .../lib/src/google_maps_flutter_android.dart | 28 ++++++- .../lib/src/messages.g.dart | 43 ++++++++++ .../pigeons/messages.dart | 10 +++ 5 files changed, 177 insertions(+), 11 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 3aab82c6f2f3..e68c41c7c021 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -49,6 +49,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugins.googlemaps.Messages.MapsApi; import io.flutter.plugins.googlemaps.Messages.MapsInspectorApi; import java.io.ByteArrayOutputStream; import java.util.ArrayList; @@ -67,6 +68,7 @@ class GoogleMapController DefaultLifecycleObserver, GoogleMapListener, GoogleMapOptionsSink, + Messages.MapsApi, MapsInspectorApi, MethodChannel.MethodCallHandler, OnMapReadyCallback, @@ -88,7 +90,7 @@ class GoogleMapController private boolean buildingsEnabled = true; private boolean disposed = false; @VisibleForTesting final float density; - private MethodChannel.Result mapReadyResult; + private @Nullable Messages.VoidResult mapReadyResult; private final Context context; private final LifecycleProvider lifecycleProvider; private final MarkersController markersController; @@ -125,6 +127,7 @@ class GoogleMapController methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_" + id); methodChannel.setMethodCallHandler(this); + MapsApi.setUp(binaryMessenger, Integer.toString(id), this); MapsInspectorApi.setUp(binaryMessenger, Integer.toString(id), this); AssetManager assetManager = context.getAssets(); this.lifecycleProvider = lifecycleProvider; @@ -203,7 +206,7 @@ public void onMapReady(@NonNull GoogleMap googleMap) { this.googleMap.setBuildingsEnabled(this.buildingsEnabled); installInvalidator(); if (mapReadyResult != null) { - mapReadyResult.success(null); + mapReadyResult.success(); mapReadyResult = null; } setGoogleMapListener(this); @@ -308,13 +311,6 @@ public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { @Override public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) { switch (call.method) { - case "map#waitForMap": - if (googleMap != null) { - result.success(null); - return; - } - mapReadyResult = result; - break; case "map#update": { Convert.interpretGoogleMapOptions(call.argument("options"), this); @@ -602,6 +598,7 @@ public void dispose() { } disposed = true; methodChannel.setMethodCallHandler(null); + MapsApi.setUp(binaryMessenger, Integer.toString(id), null); MapsInspectorApi.setUp(binaryMessenger, Integer.toString(id), null); setGoogleMapListener(null); setMarkerCollectionListener(null); @@ -1016,6 +1013,17 @@ private boolean updateMapStyle(String style) { return set; } + /** MapsApi implementation */ + @Override + public void waitForMap(@NonNull Messages.VoidResult result) { + if (googleMap == null) { + mapReadyResult = result; + } else { + result.success(); + } + } + + /** MapsInspectorApi implementation */ @Override public @NonNull Boolean areBuildingsEnabled() { return googleMap.isBuildingsEnabled(); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index 4b5b8ad948d1..9a2489045848 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -605,6 +605,87 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } } + /** Asynchronous error handling return type for non-nullable API method returns. */ + public interface Result { + /** Success case callback method for handling returns. */ + void success(@NonNull T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for nullable API method returns. */ + public interface NullableResult { + /** Success case callback method for handling returns. */ + void success(@Nullable T result); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** Asynchronous error handling return type for void API method returns. */ + public interface VoidResult { + /** Success case callback method for handling returns. */ + void success(); + + /** Failure case callback method for handling errors. */ + void error(@NonNull Throwable error); + } + /** + * Interface for non-test interactions with the native SDK. + * + *

For test-only state queries, see [MapsInspectorApi]. + * + *

Generated interface from Pigeon that represents a handler of messages from Flutter. + */ + public interface MapsApi { + /** Returns once the map instance is available. */ + void waitForMap(@NonNull VoidResult result); + + /** The codec used by MapsApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** Sets up an instance of `MapsApi` to handle messages through the `binaryMessenger`. */ + static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable MapsApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable MapsApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.waitForMap" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + VoidResult resultCallback = + new VoidResult() { + public void success() { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.waitForMap(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } /** * Inspector API only intended for use in integration tests. * 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 59993bc88d1e..c39b7bef1cf3 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 @@ -68,6 +68,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { // Every method call passes the int mapId final Map _channels = {}; + final Map _hostMaps = {}; + /// Accesses the MethodChannel associated to the passed mapId. MethodChannel _channel(int mapId) { final MethodChannel? channel = _channels[mapId]; @@ -77,6 +79,15 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return channel; } + /// Accesses the MapsApi associated to the passed mapId. + MapsApi _hostApi(int mapId) { + final MapsApi? api = _hostMaps[mapId]; + if (api == null) { + throw UnknownMapIDError(mapId); + } + return api; + } + // Keep a collection of mapId to a map of TileOverlays. final Map> _tileOverlays = >{}; @@ -94,10 +105,23 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { return channel; } + /// Returns the API instance for [mapId], creating it if it doesn't already + /// exist. + @visibleForTesting + MapsApi ensureApiInitialized(int mapId) { + MapsApi? api = _hostMaps[mapId]; + if (api == null) { + api = MapsApi(messageChannelSuffix: mapId.toString()); + _hostMaps[mapId] = api; + } + return api; + } + @override Future init(int mapId) { - final MethodChannel channel = ensureChannelInitialized(mapId); - return channel.invokeMethod('map#waitForMap'); + ensureChannelInitialized(mapId); + final MapsApi hostApi = ensureApiInitialized(mapId); + return hostApi.waitForMap(); } @override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index b7d501e2889f..083f33fcefa5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -216,6 +216,49 @@ class _PigeonCodec extends StandardMessageCodec { } } +/// Interface for non-test interactions with the native SDK. +/// +/// For test-only state queries, see [MapsInspectorApi]. +class MapsApi { + /// Constructor for [MapsApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + MapsApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : __pigeon_binaryMessenger = binaryMessenger, + __pigeon_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String __pigeon_messageChannelSuffix; + + /// Returns once the map instance is available. + Future waitForMap() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.waitForMap$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } +} + /// Inspector API only intended for use in integration tests. class MapsInspectorApi { /// Constructor for [MapsInspectorApi]. The [binaryMessenger] named argument is diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 7cea60730e39..211633f16c1e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -68,6 +68,16 @@ class PlatformZoomRange { final double max; } +/// Interface for non-test interactions with the native SDK. +/// +/// For test-only state queries, see [MapsInspectorApi]. +@HostApi() +abstract class MapsApi { + /// Returns once the map instance is available. + @async + void waitForMap(); +} + /// Inspector API only intended for use in integration tests. @HostApi() abstract class MapsInspectorApi { From e894681814ca13bdeefbd4003d097e645ddcb6c4 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 20 Jun 2024 14:16:36 -0400 Subject: [PATCH 2/9] Add non-null assertions to inspector APIs --- .../googlemaps/GoogleMapController.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index e68c41c7c021..a71156b79459 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -1026,37 +1026,37 @@ public void waitForMap(@NonNull Messages.VoidResult result) { /** MapsInspectorApi implementation */ @Override public @NonNull Boolean areBuildingsEnabled() { - return googleMap.isBuildingsEnabled(); + return Objects.requireNonNull(googleMap).isBuildingsEnabled(); } @Override public @NonNull Boolean areRotateGesturesEnabled() { - return googleMap.getUiSettings().isRotateGesturesEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isRotateGesturesEnabled(); } @Override public @NonNull Boolean areZoomControlsEnabled() { - return googleMap.getUiSettings().isZoomControlsEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isZoomControlsEnabled(); } @Override public @NonNull Boolean areScrollGesturesEnabled() { - return googleMap.getUiSettings().isScrollGesturesEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isScrollGesturesEnabled(); } @Override public @NonNull Boolean areTiltGesturesEnabled() { - return googleMap.getUiSettings().isTiltGesturesEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isTiltGesturesEnabled(); } @Override public @NonNull Boolean areZoomGesturesEnabled() { - return googleMap.getUiSettings().isZoomGesturesEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isZoomGesturesEnabled(); } @Override public @NonNull Boolean isCompassEnabled() { - return googleMap.getUiSettings().isCompassEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isCompassEnabled(); } @Override @@ -1066,17 +1066,17 @@ public Boolean isLiteModeEnabled() { @Override public @NonNull Boolean isMapToolbarEnabled() { - return googleMap.getUiSettings().isMapToolbarEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isMapToolbarEnabled(); } @Override public @NonNull Boolean isMyLocationButtonEnabled() { - return googleMap.getUiSettings().isMyLocationButtonEnabled(); + return Objects.requireNonNull(googleMap).getUiSettings().isMyLocationButtonEnabled(); } @Override public @NonNull Boolean isTrafficEnabled() { - return googleMap.isTrafficEnabled(); + return Objects.requireNonNull(googleMap).isTrafficEnabled(); } @Override @@ -1096,8 +1096,8 @@ public Boolean isLiteModeEnabled() { @Override public @NonNull Messages.PlatformZoomRange getZoomRange() { return new Messages.PlatformZoomRange.Builder() - .setMin((double) googleMap.getMinZoomLevel()) - .setMax((double) googleMap.getMaxZoomLevel()) + .setMin((double) Objects.requireNonNull(googleMap).getMinZoomLevel()) + .setMax((double) Objects.requireNonNull(googleMap).getMaxZoomLevel()) .build(); } From 86103a1078beee7b31caa210119824210dec65bb Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 20 Jun 2024 15:35:00 -0400 Subject: [PATCH 3/9] Convert setting style --- .../googlemaps/GoogleMapController.java | 36 ++++------ .../flutter/plugins/googlemaps/Messages.java | 67 ++++++++++++++++++ .../lib/src/google_maps_flutter_android.dart | 18 +++-- .../lib/src/messages.g.dart | 68 +++++++++++++++++++ .../pigeons/messages.dart | 14 ++++ 5 files changed, 174 insertions(+), 29 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index a71156b79459..2b1aad07e1c9 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -109,7 +109,7 @@ class GoogleMapController private List> initialTileOverlays; // Null except between initialization and onMapReady. private @Nullable String initialMapStyle; - private @Nullable String lastStyleError; + private boolean lastSetStyleSucceeded; @VisibleForTesting List initialPadding; GoogleMapController( @@ -472,24 +472,6 @@ public void onSnapshotReady(Bitmap bitmap) { result.success(googleMap.getCameraPosition().zoom); break; } - case "map#setStyle": - { - Object arg = call.arguments; - final String style = arg instanceof String ? (String) arg : null; - final boolean mapStyleSet = updateMapStyle(style); - ArrayList mapStyleResult = new ArrayList<>(2); - mapStyleResult.add(mapStyleSet); - if (!mapStyleSet) { - mapStyleResult.add(lastStyleError); - } - result.success(mapStyleResult); - break; - } - case "map#getStyleError": - { - result.success(lastStyleError); - break; - } case "tileOverlays#update": { List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); @@ -1007,10 +989,8 @@ private boolean updateMapStyle(String style) { // Dart passes an empty string to indicate that the style should be cleared. final MapStyleOptions mapStyleOptions = style == null || style.isEmpty() ? null : new MapStyleOptions(style); - final boolean set = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions); - lastStyleError = - set ? null : "Unable to set the map style. Please check console logs for errors."; - return set; + lastSetStyleSucceeded = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions); + return lastSetStyleSucceeded; } /** MapsApi implementation */ @@ -1023,6 +1003,16 @@ public void waitForMap(@NonNull Messages.VoidResult result) { } } + @Override + public @NonNull Boolean setStyle(@NonNull String style) { + return updateMapStyle(style); + } + + @Override + public @NonNull Boolean didLastStyleSucceed() { + return lastSetStyleSucceeded; + } + /** MapsInspectorApi implementation */ @Override public @NonNull Boolean areBuildingsEnabled() { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index 9a2489045848..dea2c3d2966b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -639,6 +639,23 @@ public interface VoidResult { public interface MapsApi { /** Returns once the map instance is available. */ void waitForMap(@NonNull VoidResult result); + /** + * Sets the style to the given map style string, where an empty string indicates that the style + * should be cleared. + * + *

Returns false if there was an error setting the style, such as an invalid style string. + */ + @NonNull + Boolean setStyle(@NonNull String style); + /** + * Returns true if the last attempt to set a style, either via initial map style or setMapStyle, + * succeeded. + * + *

This allows checking asynchronously for initial style failures, as there is no way to + * return failures from map initialization. + */ + @NonNull + Boolean didLastStyleSucceed(); /** The codec used by MapsApi. */ static @NonNull MessageCodec getCodec() { @@ -684,6 +701,56 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.setStyle" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String styleArg = (String) args.get(0); + try { + Boolean output = api.setStyle(styleArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.didLastStyleSucceed" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Boolean output = api.didLastStyleSucceed(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** 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 c39b7bef1cf3..e835adb837b0 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 @@ -452,11 +452,9 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { String? mapStyle, { required int mapId, }) async { - final List successAndError = (await _channel(mapId) - .invokeMethod>('map#setStyle', mapStyle))!; - final bool success = successAndError[0] as bool; + final bool success = await _hostApi(mapId).setStyle(mapStyle ?? ''); if (!success) { - throw MapStyleException(successAndError[1] as String); + throw const MapStyleException(_setStyleFailureMessage); } } @@ -538,8 +536,10 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { } @override - Future getStyleError({required int mapId}) { - return _channel(mapId).invokeMethod('map#getStyleError'); + Future getStyleError({required int mapId}) async { + return (await _hostApi(mapId).didLastStyleSucceed()) + ? null + : _setStyleFailureMessage; } /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the @@ -882,3 +882,9 @@ class AndroidMapRendererException implements Exception { @override String toString() => 'AndroidMapRendererException($message)'; } + +/// The error message to use for style failures. Unlike iOS, Android does not +/// provide an API to get style failure information, it's just logged to the +/// console, so there's no platform call needed. +const String _setStyleFailureMessage = + 'Unable to set the map style. Please check console logs for errors.'; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index 083f33fcefa5..c647712b8245 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -257,6 +257,74 @@ class MapsApi { return; } } + + /// Sets the style to the given map style string, where an empty string + /// indicates that the style should be cleared. + /// + /// Returns false if there was an error setting the style, such as an invalid + /// style string. + Future setStyle(String style) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.setStyle$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([style]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + /// Returns true if the last attempt to set a style, either via initial map + /// style or setMapStyle, succeeded. + /// + /// This allows checking asynchronously for initial style failures, as there + /// is no way to return failures from map initialization. + Future didLastStyleSucceed() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.didLastStyleSucceed$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } } /// Inspector API only intended for use in integration tests. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 211633f16c1e..6b21137d5093 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -76,6 +76,20 @@ abstract class MapsApi { /// Returns once the map instance is available. @async void waitForMap(); + + /// Sets the style to the given map style string, where an empty string + /// indicates that the style should be cleared. + /// + /// Returns false if there was an error setting the style, such as an invalid + /// style string. + bool setStyle(String style); + + /// Returns true if the last attempt to set a style, either via initial map + /// style or setMapStyle, succeeded. + /// + /// This allows checking asynchronously for initial style failures, as there + /// is no way to return failures from map initialization. + bool didLastStyleSucceed(); } /// Inspector API only intended for use in integration tests. From b606e3dbafaa7df008deaea9a205df7567e2ef7f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 20 Jun 2024 15:46:23 -0400 Subject: [PATCH 4/9] Convert info window management --- .../googlemaps/GoogleMapController.java | 34 ++++---- .../plugins/googlemaps/MarkersController.java | 31 +++---- .../flutter/plugins/googlemaps/Messages.java | 85 +++++++++++++++++++ .../lib/src/google_maps_flutter_android.dart | 12 +-- .../lib/src/messages.g.dart | 81 ++++++++++++++++++ .../pigeons/messages.dart | 10 +++ 6 files changed, 210 insertions(+), 43 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 2b1aad07e1c9..619090e6025d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -403,24 +403,6 @@ public void onSnapshotReady(Bitmap bitmap) { result.success(null); break; } - case "markers#showInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.showMarkerInfoWindow((String) markerId, result); - break; - } - case "markers#hideInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.hideMarkerInfoWindow((String) markerId, result); - break; - } - case "markers#isInfoWindowShown": - { - Object markerId = call.argument("markerId"); - markersController.isInfoWindowShown((String) markerId, result); - break; - } case "clusterManagers#update": { List clusterManagersToAdd = call.argument("clusterManagersToAdd"); @@ -1003,6 +985,22 @@ public void waitForMap(@NonNull Messages.VoidResult result) { } } + @Override + public void showInfoWindow(@NonNull String markerId) { + markersController.showMarkerInfoWindow(markerId); + } + + @Override + public void hideInfoWindow(@NonNull String markerId) { + markersController.hideMarkerInfoWindow(markerId); + } + + @NonNull + @Override + public Boolean isInfoWindowShown(@NonNull String markerId) { + return markersController.isInfoWindowShown(markerId); + } + @Override public @NonNull Boolean setStyle(@NonNull String style) { return updateMapStyle(style); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java index a95f2f34482c..97650a18d714 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java @@ -92,33 +92,30 @@ private void removeMarker(String markerId) { } } - void showMarkerInfoWindow(String markerId, MethodChannel.Result result) { + void showMarkerInfoWindow(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - markerController.showInfoWindow(); - result.success(null); - } else { - result.error("Invalid markerId", "showInfoWindow called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError( + "Invalid markerId", "showInfoWindow called with invalid markerId", null); } + markerController.showInfoWindow(); } - void hideMarkerInfoWindow(String markerId, MethodChannel.Result result) { + void hideMarkerInfoWindow(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - markerController.hideInfoWindow(); - result.success(null); - } else { - result.error("Invalid markerId", "hideInfoWindow called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError( + "Invalid markerId", "hideInfoWindow called with invalid markerId", null); } + markerController.hideInfoWindow(); } - void isInfoWindowShown(String markerId, MethodChannel.Result result) { + boolean isInfoWindowShown(String markerId) { MarkerController markerController = markerIdToController.get(markerId); - if (markerController != null) { - result.success(markerController.isInfoWindowShown()); - } else { - result.error("Invalid markerId", "isInfoWindowShown called with invalid markerId", null); + if (markerController == null) { + throw new Messages.FlutterError("Invalid markerId", "isInfoWindowShown called with invalid markerId", null); } + return markerController.isInfoWindowShown(); } boolean onMapsMarkerTap(String googleMarkerId) { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index dea2c3d2966b..e0a5a25ae036 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -639,6 +639,13 @@ public interface VoidResult { public interface MapsApi { /** Returns once the map instance is available. */ void waitForMap(@NonNull VoidResult result); + /** Show the info window for the marker with the given ID. */ + void showInfoWindow(@NonNull String markerId); + /** Hide the info window for the marker with the given ID. */ + void hideInfoWindow(@NonNull String markerId); + /** Returns true if the marker with the given ID is currently displaying its info window. */ + @NonNull + Boolean isInfoWindowShown(@NonNull String markerId); /** * Sets the style to the given map style string, where an empty string indicates that the style * should be cleared. @@ -701,6 +708,84 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.showInfoWindow" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + api.showInfoWindow(markerIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.hideInfoWindow" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + api.hideInfoWindow(markerIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.isInfoWindowShown" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String markerIdArg = (String) args.get(0); + try { + Boolean output = api.isInfoWindowShown(markerIdArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( 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 e835adb837b0..48f605298f01 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 @@ -498,8 +498,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MarkerId markerId, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'markers#showInfoWindow', {'markerId': markerId.value}); + return _hostApi(mapId).showInfoWindow(markerId.value); } @override @@ -507,18 +506,15 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MarkerId markerId, { required int mapId, }) { - return _channel(mapId).invokeMethod( - 'markers#hideInfoWindow', {'markerId': markerId.value}); + return _hostApi(mapId).hideInfoWindow(markerId.value); } @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, - }) async { - return (await _channel(mapId).invokeMethod( - 'markers#isInfoWindowShown', - {'markerId': markerId.value}))!; + }) { + return _hostApi(mapId).isInfoWindowShown(markerId.value); } @override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index c647712b8245..8b2682a3c3ac 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -258,6 +258,87 @@ class MapsApi { } } + /// Show the info window for the marker with the given ID. + Future showInfoWindow(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.showInfoWindow$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Hide the info window for the marker with the given ID. + Future hideInfoWindow(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.hideInfoWindow$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Returns true if the marker with the given ID is currently displaying its + /// info window. + Future isInfoWindowShown(String markerId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.isInfoWindowShown$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([markerId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + /// Sets the style to the given map style string, where an empty string /// indicates that the style should be cleared. /// diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 6b21137d5093..41fc3c9889b1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -77,6 +77,16 @@ abstract class MapsApi { @async void waitForMap(); + /// Show the info window for the marker with the given ID. + void showInfoWindow(String markerId); + + /// Hide the info window for the marker with the given ID. + void hideInfoWindow(String markerId); + + /// Returns true if the marker with the given ID is currently displaying its + /// info window. + bool isInfoWindowShown(String markerId); + /// Sets the style to the given map style string, where an empty string /// indicates that the style should be cleared. /// From f08e80baffb04b1ed13b2beb015144d459acd903 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 21 Jun 2024 10:19:35 -0400 Subject: [PATCH 5/9] Convert getters --- .../flutter/plugins/googlemaps/Convert.java | 21 +- .../googlemaps/GoogleMapController.java | 92 +++---- .../plugins/googlemaps/MarkersController.java | 3 +- .../flutter/plugins/googlemaps/Messages.java | 254 ++++++++++++++++-- .../lib/src/google_maps_flutter_android.dart | 42 +-- .../lib/src/messages.g.dart | 174 +++++++++++- .../pigeons/messages.dart | 26 +- 7 files changed, 494 insertions(+), 118 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index dee95907724a..e1817597f16c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -439,11 +439,15 @@ static Object latLngToJson(LatLng latLng) { static Messages.PlatformLatLng latLngToPigeon(LatLng latLng) { return new Messages.PlatformLatLng.Builder() - .setLat(latLng.latitude) - .setLng(latLng.longitude) + .setLatitude(latLng.latitude) + .setLongitude(latLng.longitude) .build(); } + static LatLng latLngFromPigeon(Messages.PlatformLatLng latLng) { + return new LatLng(latLng.getLatitude(), latLng.getLongitude()); + } + static Object clusterToJson(String clusterManagerId, Cluster cluster) { int clusterSize = cluster.getSize(); LatLngBounds.Builder latLngBoundsBuilder = LatLngBounds.builder(); @@ -500,17 +504,12 @@ static LatLng toLatLng(Object o) { return new LatLng(toDouble(data.get(0)), toDouble(data.get(1))); } - static Point toPoint(Object o) { - Object x = toMap(o).get("x"); - Object y = toMap(o).get("y"); - return new Point((int) x, (int) y); + static Point pointFromPigeon(Messages.PlatformPoint point) { + return new Point(point.getX().intValue(), point.getY().intValue()); } - static Map pointToJson(Point point) { - final Map data = new HashMap<>(2); - data.put("x", point.x); - data.put("y", point.y); - return data; + static Messages.PlatformPoint pointToPigeon(Point point) { + return new Messages.PlatformPoint.Builder().setX((long) point.x).setY((long) point.y).build(); } private static LatLngBounds toLatLngBounds(Object o) { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 619090e6025d..d08be26f86a0 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -49,6 +49,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugins.googlemaps.Messages.FlutterError; import io.flutter.plugins.googlemaps.Messages.MapsApi; import io.flutter.plugins.googlemaps.Messages.MapsInspectorApi; import java.io.ByteArrayOutputStream; @@ -68,7 +69,7 @@ class GoogleMapController DefaultLifecycleObserver, GoogleMapListener, GoogleMapOptionsSink, - Messages.MapsApi, + MapsApi, MapsInspectorApi, MethodChannel.MethodCallHandler, OnMapReadyCallback, @@ -317,45 +318,6 @@ public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) result.success(Convert.cameraPositionToJson(getCameraPosition())); break; } - case "map#getVisibleRegion": - { - if (googleMap != null) { - LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; - result.success(Convert.latLngBoundsToJson(latLngBounds)); - } else { - result.error( - "GoogleMap uninitialized", - "getVisibleRegion called prior to map initialization", - null); - } - break; - } - case "map#getScreenCoordinate": - { - if (googleMap != null) { - LatLng latLng = Convert.toLatLng(call.arguments); - Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); - result.success(Convert.pointToJson(screenLocation)); - } else { - result.error( - "GoogleMap uninitialized", - "getScreenCoordinate called prior to map initialization", - null); - } - break; - } - case "map#getLatLng": - { - if (googleMap != null) { - Point point = Convert.toPoint(call.arguments); - LatLng latLng = googleMap.getProjection().fromScreenLocation(point); - result.success(Convert.latLngToJson(latLng)); - } else { - result.error( - "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); - } - break; - } case "map#takeSnapshot": { if (googleMap != null) { @@ -449,11 +411,6 @@ public void onSnapshotReady(Bitmap bitmap) { result.success(null); break; } - case "map#getZoomLevel": - { - result.success(googleMap.getCameraPosition().zoom); - break; - } case "tileOverlays#update": { List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); @@ -985,6 +942,51 @@ public void waitForMap(@NonNull Messages.VoidResult result) { } } + @Override + public @NonNull Messages.PlatformPoint getScreenCoordinate( + @NonNull Messages.PlatformLatLng latLng) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", + "getScreenCoordinate called prior to map initialization", + null); + } + Point screenLocation = + googleMap.getProjection().toScreenLocation(Convert.latLngFromPigeon(latLng)); + return Convert.pointToPigeon(screenLocation); + } + + @Override + public @NonNull Messages.PlatformLatLng getLatLng( + @NonNull Messages.PlatformPoint screenCoordinate) { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); + } + LatLng latLng = + googleMap.getProjection().fromScreenLocation(Convert.pointFromPigeon(screenCoordinate)); + return Convert.latLngToPigeon(latLng); + } + + @Override + public @NonNull Messages.PlatformLatLngBounds getVisibleRegion() { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getVisibleRegion called prior to map initialization", null); + } + LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; + return Convert.latLngBoundsToPigeon(latLngBounds); + } + + @Override + public @NonNull Double getZoomLevel() { + if (googleMap == null) { + throw new FlutterError( + "GoogleMap uninitialized", "getZoomLevel called prior to map initialization", null); + } + return (double) googleMap.getCameraPosition().zoom; + } + @Override public void showInfoWindow(@NonNull String markerId) { markersController.showMarkerInfoWindow(markerId); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java index 97650a18d714..0f47fecaa500 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java @@ -113,7 +113,8 @@ void hideMarkerInfoWindow(String markerId) { boolean isInfoWindowShown(String markerId) { MarkerController markerController = markerIdToController.get(markerId); if (markerController == null) { - throw new Messages.FlutterError("Invalid markerId", "isInfoWindowShown called with invalid markerId", null); + throw new Messages.FlutterError( + "Invalid markerId", "isInfoWindowShown called with invalid markerId", null); } return markerController.isInfoWindowShown(); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index e0a5a25ae036..1fcd216fd03d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -70,30 +70,30 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { *

Generated class from Pigeon that represents data sent in messages. */ public static final class PlatformLatLng { - private @NonNull Double lat; + private @NonNull Double latitude; - public @NonNull Double getLat() { - return lat; + public @NonNull Double getLatitude() { + return latitude; } - public void setLat(@NonNull Double setterArg) { + public void setLatitude(@NonNull Double setterArg) { if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"lat\" is null."); + throw new IllegalStateException("Nonnull field \"latitude\" is null."); } - this.lat = setterArg; + this.latitude = setterArg; } - private @NonNull Double lng; + private @NonNull Double longitude; - public @NonNull Double getLng() { - return lng; + public @NonNull Double getLongitude() { + return longitude; } - public void setLng(@NonNull Double setterArg) { + public void setLongitude(@NonNull Double setterArg) { if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"lng\" is null."); + throw new IllegalStateException("Nonnull field \"longitude\" is null."); } - this.lng = setterArg; + this.longitude = setterArg; } /** Constructor is non-public to enforce null safety; use Builder. */ @@ -101,26 +101,26 @@ public void setLng(@NonNull Double setterArg) { public static final class Builder { - private @Nullable Double lat; + private @Nullable Double latitude; @CanIgnoreReturnValue - public @NonNull Builder setLat(@NonNull Double setterArg) { - this.lat = setterArg; + public @NonNull Builder setLatitude(@NonNull Double setterArg) { + this.latitude = setterArg; return this; } - private @Nullable Double lng; + private @Nullable Double longitude; @CanIgnoreReturnValue - public @NonNull Builder setLng(@NonNull Double setterArg) { - this.lng = setterArg; + public @NonNull Builder setLongitude(@NonNull Double setterArg) { + this.longitude = setterArg; return this; } public @NonNull PlatformLatLng build() { PlatformLatLng pigeonReturn = new PlatformLatLng(); - pigeonReturn.setLat(lat); - pigeonReturn.setLng(lng); + pigeonReturn.setLatitude(latitude); + pigeonReturn.setLongitude(longitude); return pigeonReturn; } } @@ -128,17 +128,17 @@ public static final class Builder { @NonNull ArrayList toList() { ArrayList toListResult = new ArrayList(2); - toListResult.add(lat); - toListResult.add(lng); + toListResult.add(latitude); + toListResult.add(longitude); return toListResult; } static @NonNull PlatformLatLng fromList(@NonNull ArrayList __pigeon_list) { PlatformLatLng pigeonResult = new PlatformLatLng(); - Object lat = __pigeon_list.get(0); - pigeonResult.setLat((Double) lat); - Object lng = __pigeon_list.get(1); - pigeonResult.setLng((Double) lng); + Object latitude = __pigeon_list.get(0); + pigeonResult.setLatitude((Double) latitude); + Object longitude = __pigeon_list.get(1); + pigeonResult.setLongitude((Double) longitude); return pigeonResult; } } @@ -351,6 +351,85 @@ ArrayList toList() { } } + /** + * Pigeon representation of an x,y coordinate. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPoint { + private @NonNull Long x; + + public @NonNull Long getX() { + return x; + } + + public void setX(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"x\" is null."); + } + this.x = setterArg; + } + + private @NonNull Long y; + + public @NonNull Long getY() { + return y; + } + + public void setY(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"y\" is null."); + } + this.y = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPoint() {} + + public static final class Builder { + + private @Nullable Long x; + + @CanIgnoreReturnValue + public @NonNull Builder setX(@NonNull Long setterArg) { + this.x = setterArg; + return this; + } + + private @Nullable Long y; + + @CanIgnoreReturnValue + public @NonNull Builder setY(@NonNull Long setterArg) { + this.y = setterArg; + return this; + } + + public @NonNull PlatformPoint build() { + PlatformPoint pigeonReturn = new PlatformPoint(); + pigeonReturn.setX(x); + pigeonReturn.setY(y); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(x); + toListResult.add(y); + return toListResult; + } + + static @NonNull PlatformPoint fromList(@NonNull ArrayList __pigeon_list) { + PlatformPoint pigeonResult = new PlatformPoint(); + Object x = __pigeon_list.get(0); + pigeonResult.setX((x == null) ? null : ((x instanceof Integer) ? (Integer) x : (Long) x)); + Object y = __pigeon_list.get(1); + pigeonResult.setY((y == null) ? null : ((y instanceof Integer) ? (Integer) y : (Long) y)); + return pigeonResult; + } + } + /** * Pigeon equivalent of native TileOverlay properties. * @@ -574,8 +653,10 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 131: return PlatformCluster.fromList((ArrayList) readValue(buffer)); case (byte) 132: - return PlatformTileLayer.fromList((ArrayList) readValue(buffer)); + return PlatformPoint.fromList((ArrayList) readValue(buffer)); case (byte) 133: + return PlatformTileLayer.fromList((ArrayList) readValue(buffer)); + case (byte) 134: return PlatformZoomRange.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -593,11 +674,14 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof PlatformCluster) { stream.write(131); writeValue(stream, ((PlatformCluster) value).toList()); - } else if (value instanceof PlatformTileLayer) { + } else if (value instanceof PlatformPoint) { stream.write(132); + writeValue(stream, ((PlatformPoint) value).toList()); + } else if (value instanceof PlatformTileLayer) { + stream.write(133); writeValue(stream, ((PlatformTileLayer) value).toList()); } else if (value instanceof PlatformZoomRange) { - stream.write(133); + stream.write(134); writeValue(stream, ((PlatformZoomRange) value).toList()); } else { super.writeValue(stream, value); @@ -639,6 +723,18 @@ public interface VoidResult { public interface MapsApi { /** Returns once the map instance is available. */ void waitForMap(@NonNull VoidResult result); + /** Gets the screen coordinate for the given map location. */ + @NonNull + PlatformPoint getScreenCoordinate(@NonNull PlatformLatLng latLng); + /** Gets the map location for the given screen coordinate. */ + @NonNull + PlatformLatLng getLatLng(@NonNull PlatformPoint screenCoordinate); + /** Gets the map region currently displayed on the map. */ + @NonNull + PlatformLatLngBounds getVisibleRegion(); + /** Gets the current map zoom level. */ + @NonNull + Double getZoomLevel(); /** Show the info window for the marker with the given ID. */ void showInfoWindow(@NonNull String markerId); /** Hide the info window for the marker with the given ID. */ @@ -708,6 +804,106 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getScreenCoordinate" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformLatLng latLngArg = (PlatformLatLng) args.get(0); + try { + PlatformPoint output = api.getScreenCoordinate(latLngArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getLatLng" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + PlatformPoint screenCoordinateArg = (PlatformPoint) args.get(0); + try { + PlatformLatLng output = api.getLatLng(screenCoordinateArg); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getVisibleRegion" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + PlatformLatLngBounds output = api.getVisibleRegion(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getZoomLevel" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + try { + Double output = api.getZoomLevel(); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( 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 48f605298f01..925bafcf7aba 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 @@ -462,12 +462,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { Future getVisibleRegion({ required int mapId, }) async { - final Map latLngBounds = (await _channel(mapId) - .invokeMapMethod('map#getVisibleRegion'))!; - final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; - final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; - - return LatLngBounds(northeast: northeast, southwest: southwest); + return _latLngBoundsFromPlatformLatLngBounds( + await _hostApi(mapId).getVisibleRegion()); } @override @@ -475,11 +471,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { LatLng latLng, { required int mapId, }) async { - final Map point = (await _channel(mapId) - .invokeMapMethod( - 'map#getScreenCoordinate', latLng.toJson()))!; - - return ScreenCoordinate(x: point['x']!, y: point['y']!); + return _screenCoordinateFromPlatformPoint(await _hostApi(mapId) + .getScreenCoordinate(_platformLatLngFromLatLng(latLng))); } @override @@ -487,10 +480,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { ScreenCoordinate screenCoordinate, { required int mapId, }) async { - final List latLng = (await _channel(mapId) - .invokeMethod>( - 'map#getLatLng', screenCoordinate.toJson()))!; - return LatLng(latLng[0] as double, latLng[1] as double); + return _latLngFromPlatformLatLng(await _hostApi(mapId) + .getLatLng(_platformPointFromScreenCoordinate(screenCoordinate))); } @override @@ -520,8 +511,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { @override Future getZoomLevel({ required int mapId, - }) async { - return (await _channel(mapId).invokeMethod('map#getZoomLevel'))!; + }) { + return _hostApi(mapId).getZoomLevel(); } @override @@ -790,7 +781,22 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { } static LatLng _latLngFromPlatformLatLng(PlatformLatLng latLng) { - return LatLng(latLng.lat, latLng.lng); + return LatLng(latLng.latitude, latLng.longitude); + } + + static PlatformLatLng _platformLatLngFromLatLng(LatLng latLng) { + return PlatformLatLng( + latitude: latLng.latitude, longitude: latLng.longitude); + } + + static ScreenCoordinate _screenCoordinateFromPlatformPoint( + PlatformPoint point) { + return ScreenCoordinate(x: point.x, y: point.y); + } + + static PlatformPoint _platformPointFromScreenCoordinate( + ScreenCoordinate coordinate) { + return PlatformPoint(x: coordinate.x, y: coordinate.y); } static LatLngBounds _latLngBoundsFromPlatformLatLngBounds( diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index 8b2682a3c3ac..45a3b785f684 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -21,26 +21,26 @@ PlatformException _createConnectionError(String channelName) { /// Pigeon equivalent of LatLng. class PlatformLatLng { PlatformLatLng({ - required this.lat, - required this.lng, + required this.latitude, + required this.longitude, }); - double lat; + double latitude; - double lng; + double longitude; Object encode() { return [ - lat, - lng, + latitude, + longitude, ]; } static PlatformLatLng decode(Object result) { result as List; return PlatformLatLng( - lat: result[0]! as double, - lng: result[1]! as double, + latitude: result[0]! as double, + longitude: result[1]! as double, ); } } @@ -109,6 +109,33 @@ class PlatformCluster { } } +/// Pigeon representation of an x,y coordinate. +class PlatformPoint { + PlatformPoint({ + required this.x, + required this.y, + }); + + int x; + + int y; + + Object encode() { + return [ + x, + y, + ]; + } + + static PlatformPoint decode(Object result) { + result as List; + return PlatformPoint( + x: result[0]! as int, + y: result[1]! as int, + ); + } +} + /// Pigeon equivalent of native TileOverlay properties. class PlatformTileLayer { PlatformTileLayer({ @@ -186,12 +213,15 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is PlatformCluster) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is PlatformTileLayer) { + } else if (value is PlatformPoint) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is PlatformZoomRange) { + } else if (value is PlatformTileLayer) { buffer.putUint8(133); writeValue(buffer, value.encode()); + } else if (value is PlatformZoomRange) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -207,8 +237,10 @@ class _PigeonCodec extends StandardMessageCodec { case 131: return PlatformCluster.decode(readValue(buffer)!); case 132: - return PlatformTileLayer.decode(readValue(buffer)!); + return PlatformPoint.decode(readValue(buffer)!); case 133: + return PlatformTileLayer.decode(readValue(buffer)!); + case 134: return PlatformZoomRange.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -258,6 +290,126 @@ class MapsApi { } } + /// Gets the screen coordinate for the given map location. + Future getScreenCoordinate(PlatformLatLng latLng) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getScreenCoordinate$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([latLng]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformPoint?)!; + } + } + + /// Gets the map location for the given screen coordinate. + Future getLatLng(PlatformPoint screenCoordinate) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getLatLng$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([screenCoordinate]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformLatLng?)!; + } + } + + /// Gets the map region currently displayed on the map. + Future getVisibleRegion() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getVisibleRegion$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as PlatformLatLngBounds?)!; + } + } + + /// Gets the current map zoom level. + Future getZoomLevel() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.getZoomLevel$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as double?)!; + } + } + /// Show the info window for the marker with the given ID. Future showInfoWindow(String markerId) async { final String __pigeon_channelName = diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 41fc3c9889b1..01cce38e22fb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -13,10 +13,10 @@ import 'package:pigeon/pigeon.dart'; /// Pigeon equivalent of LatLng. class PlatformLatLng { - PlatformLatLng({required this.lat, required this.lng}); + PlatformLatLng({required this.latitude, required this.longitude}); - final double lat; - final double lng; + final double latitude; + final double longitude; } /// Pigeon equivalent of LatLngBounds. @@ -45,6 +45,14 @@ class PlatformCluster { final List markerIds; } +/// Pigeon representation of an x,y coordinate. +class PlatformPoint { + PlatformPoint({required this.x, required this.y}); + + final int x; + final int y; +} + /// Pigeon equivalent of native TileOverlay properties. class PlatformTileLayer { PlatformTileLayer({ @@ -77,6 +85,18 @@ abstract class MapsApi { @async void waitForMap(); + /// Gets the screen coordinate for the given map location. + PlatformPoint getScreenCoordinate(PlatformLatLng latLng); + + /// Gets the map location for the given screen coordinate. + PlatformLatLng getLatLng(PlatformPoint screenCoordinate); + + /// Gets the map region currently displayed on the map. + PlatformLatLngBounds getVisibleRegion(); + + /// Gets the current map zoom level. + double getZoomLevel(); + /// Show the info window for the marker with the given ID. void showInfoWindow(String markerId); From b0dfb05fab456cffb9da59cdd4499a164dfcbf23 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 21 Jun 2024 11:16:15 -0400 Subject: [PATCH 6/9] Add real Dart unit tests --- .../lib/src/google_maps_flutter_android.dart | 17 +- .../google_maps_flutter_android/pubspec.yaml | 2 + .../google_maps_flutter_android_test.dart | 185 +++++++++++----- ...oogle_maps_flutter_android_test.mocks.dart | 204 ++++++++++++++++++ 4 files changed, 351 insertions(+), 57 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart 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 925bafcf7aba..f0ee83263b03 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 @@ -19,6 +19,11 @@ import 'utils/cluster_manager_utils.dart'; // TODO(stuartmorgan): Remove the dependency on platform interface toJson // methods. Channel serialization details should all be package-internal. +/// The non-test implementation of `_apiProvider`. +MapsApi _productionApiProvider(int mapId) { + return MapsApi(messageChannelSuffix: mapId.toString()); +} + /// Error thrown when an unknown map ID is provided to a method channel API. class UnknownMapIDError extends Error { /// Creates an assertion error with the provided [mapId] and optional @@ -55,6 +60,11 @@ enum AndroidMapRenderer { /// An implementation of [GoogleMapsFlutterPlatform] for Android. class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { + /// Creates a new Android maps implementation instance. + GoogleMapsFlutterAndroid({ + @visibleForTesting MapsApi Function(int mapId)? apiProvider, + }) : _apiProvider = apiProvider ?? _productionApiProvider; + /// Registers the Android implementation of GoogleMapsFlutterPlatform. static void registerWith() { GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid(); @@ -70,6 +80,9 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { final Map _hostMaps = {}; + // A method to create MapsApi instances, which can be overridden for testing. + final MapsApi Function(int mapId) _apiProvider; + /// Accesses the MethodChannel associated to the passed mapId. MethodChannel _channel(int mapId) { final MethodChannel? channel = _channels[mapId]; @@ -111,8 +124,8 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { MapsApi ensureApiInitialized(int mapId) { MapsApi? api = _hostMaps[mapId]; if (api == null) { - api = MapsApi(messageChannelSuffix: mapId.toString()); - _hostMaps[mapId] = api; + api = _apiProvider(mapId); + _hostMaps[mapId] ??= api; } return api; } 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 6705a98896d2..ff14604e0603 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -26,8 +26,10 @@ dependencies: dev_dependencies: async: ^2.5.0 + build_runner: ^2.3.3 flutter_test: sdk: flutter + mockito: 5.4.4 pigeon: ^20.0.1 plugin_platform_interface: ^2.1.7 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 946d84d76619..a20f7254ac0a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -8,33 +8,23 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; +import 'package:google_maps_flutter_android/src/messages.g.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'google_maps_flutter_android_test.mocks.dart'; + +@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late List log; - - setUp(() async { - log = []; - }); - - /// Initializes a map with the given ID and canned responses, logging all - /// calls to [log]. - void configureMockMap( - GoogleMapsFlutterAndroid maps, { - required int mapId, - required Future? Function(MethodCall call) handler, - }) { - final MethodChannel channel = maps.ensureChannelInitialized(mapId); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (MethodCall methodCall) { - log.add(methodCall.method); - return handler(methodCall); - }, - ); + (GoogleMapsFlutterAndroid, MockMapsApi) setUpMockMap({required int mapId}) { + final MockMapsApi api = MockMapsApi(); + final GoogleMapsFlutterAndroid maps = + GoogleMapsFlutterAndroid(apiProvider: (_) => api); + maps.ensureApiInitialized(mapId); + return (maps, api); } Future sendPlatformMessage( @@ -51,39 +41,124 @@ void main() { expect(GoogleMapsFlutterPlatform.instance, isA()); }); - // 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 GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); - configureMockMap(maps, mapId: mapId, - handler: (MethodCall methodCall) async { - switch (methodCall.method) { - case 'map#getLatLng': - return [1.0, 2.0]; - case 'markers#isInfoWindowShown': - return true; - case 'map#getZoomLevel': - return 2.5; - case 'map#takeSnapshot': - return null; - } - }); - - await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); - await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId); - await maps.getZoomLevel(mapId: mapId); - await maps.takeSnapshot(mapId: mapId); - // Check that all the invokeMethod calls happened. - expect(log, [ - 'map#getLatLng', - 'markers#isInfoWindowShown', - 'map#getZoomLevel', - 'map#takeSnapshot', - ]); + test('init calls waitForMap', () async { + final MockMapsApi api = MockMapsApi(); + final GoogleMapsFlutterAndroid maps = + GoogleMapsFlutterAndroid(apiProvider: (_) => api); + + await maps.init(1); + + verify(api.waitForMap()); + }); + + test('getScreenCoordinate converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + const LatLng latLng = LatLng(10, 20); + const ScreenCoordinate expectedCoord = ScreenCoordinate(x: 30, y: 40); + when(api.getScreenCoordinate(any)).thenAnswer( + (_) async => PlatformPoint(x: expectedCoord.x, y: expectedCoord.y)); + + final ScreenCoordinate coord = + await maps.getScreenCoordinate(latLng, mapId: mapId); + expect(coord, expectedCoord); + final VerificationResult verification = + verify(api.getScreenCoordinate(captureAny)); + final PlatformLatLng passedLatLng = + verification.captured[0] as PlatformLatLng; + expect(passedLatLng.latitude, latLng.latitude); + expect(passedLatLng.longitude, latLng.longitude); + }); + + test('getLatLng converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + const LatLng expectedLatLng = LatLng(10, 20); + const ScreenCoordinate coord = ScreenCoordinate(x: 30, y: 40); + when(api.getLatLng(any)).thenAnswer((_) async => PlatformLatLng( + latitude: expectedLatLng.latitude, + longitude: expectedLatLng.longitude)); + + final LatLng latLng = await maps.getLatLng(coord, mapId: mapId); + expect(latLng, expectedLatLng); + final VerificationResult verification = verify(api.getLatLng(captureAny)); + final PlatformPoint passedCoord = verification.captured[0] as PlatformPoint; + expect(passedCoord.x, coord.x); + expect(passedCoord.y, coord.y); + }); + + test('getVisibleRegion converts and passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + // Arbitrary values that are all different from each other. + final LatLngBounds expectedBounds = LatLngBounds( + southwest: const LatLng(10, 20), northeast: const LatLng(30, 40)); + when(api.getVisibleRegion()).thenAnswer((_) async => PlatformLatLngBounds( + southwest: PlatformLatLng( + latitude: expectedBounds.southwest.latitude, + longitude: expectedBounds.southwest.longitude), + northeast: PlatformLatLng( + latitude: expectedBounds.northeast.latitude, + longitude: expectedBounds.northeast.longitude))); + + final LatLngBounds bounds = await maps.getVisibleRegion(mapId: mapId); + expect(bounds, expectedBounds); + }); + + test('getZoomLevel passes values correctly', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const double expectedZoom = 4.2; + when(api.getZoomLevel()).thenAnswer((_) async => expectedZoom); + + final double zoom = await maps.getZoomLevel(mapId: mapId); + expect(zoom, expectedZoom); + }); + + test('showInfoWindow calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + await maps.showMarkerInfoWindow(const MarkerId(markedId), mapId: mapId); + + verify(api.showInfoWindow(markedId)); + }); + + test('hideInfoWindow calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + await maps.hideMarkerInfoWindow(const MarkerId(markedId), mapId: mapId); + + verify(api.hideInfoWindow(markedId)); + }); + + test('isInfoWindowShown calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String markedId = 'a_marker'; + when(api.isInfoWindowShown(markedId)).thenAnswer((_) async => true); + + expect( + await maps.isMarkerInfoWindowShown(const MarkerId(markedId), + mapId: mapId), + true); }); test('markers send drag event to correct streams', () async { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart new file mode 100644 index 000000000000..8628c96b46e6 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart @@ -0,0 +1,204 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in google_maps_flutter_android/test/google_maps_flutter_android_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:google_maps_flutter_android/src/messages.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformPoint_0 extends _i1.SmartFake implements _i2.PlatformPoint { + _FakePlatformPoint_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformLatLng_1 extends _i1.SmartFake + implements _i2.PlatformLatLng { + _FakePlatformLatLng_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePlatformLatLngBounds_2 extends _i1.SmartFake + implements _i2.PlatformLatLngBounds { + _FakePlatformLatLngBounds_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [MapsApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMapsApi extends _i1.Mock implements _i2.MapsApi { + @override + _i3.Future waitForMap() => (super.noSuchMethod( + Invocation.method( + #waitForMap, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future<_i2.PlatformPoint> getScreenCoordinate( + _i2.PlatformLatLng? latLng) => + (super.noSuchMethod( + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + returnValue: _i3.Future<_i2.PlatformPoint>.value(_FakePlatformPoint_0( + this, + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.PlatformPoint>.value(_FakePlatformPoint_0( + this, + Invocation.method( + #getScreenCoordinate, + [latLng], + ), + )), + ) as _i3.Future<_i2.PlatformPoint>); + + @override + _i3.Future<_i2.PlatformLatLng> getLatLng( + _i2.PlatformPoint? screenCoordinate) => + (super.noSuchMethod( + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + returnValue: _i3.Future<_i2.PlatformLatLng>.value(_FakePlatformLatLng_1( + this, + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + )), + returnValueForMissingStub: + _i3.Future<_i2.PlatformLatLng>.value(_FakePlatformLatLng_1( + this, + Invocation.method( + #getLatLng, + [screenCoordinate], + ), + )), + ) as _i3.Future<_i2.PlatformLatLng>); + + @override + _i3.Future<_i2.PlatformLatLngBounds> getVisibleRegion() => + (super.noSuchMethod( + Invocation.method( + #getVisibleRegion, + [], + ), + returnValue: _i3.Future<_i2.PlatformLatLngBounds>.value( + _FakePlatformLatLngBounds_2( + this, + Invocation.method( + #getVisibleRegion, + [], + ), + )), + returnValueForMissingStub: _i3.Future<_i2.PlatformLatLngBounds>.value( + _FakePlatformLatLngBounds_2( + this, + Invocation.method( + #getVisibleRegion, + [], + ), + )), + ) as _i3.Future<_i2.PlatformLatLngBounds>); + + @override + _i3.Future getZoomLevel() => (super.noSuchMethod( + Invocation.method( + #getZoomLevel, + [], + ), + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); + + @override + _i3.Future showInfoWindow(String? markerId) => (super.noSuchMethod( + Invocation.method( + #showInfoWindow, + [markerId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future hideInfoWindow(String? markerId) => (super.noSuchMethod( + Invocation.method( + #hideInfoWindow, + [markerId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future isInfoWindowShown(String? markerId) => (super.noSuchMethod( + Invocation.method( + #isInfoWindowShown, + [markerId], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future setStyle(String? style) => (super.noSuchMethod( + Invocation.method( + #setStyle, + [style], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future didLastStyleSucceed() => (super.noSuchMethod( + Invocation.method( + #didLastStyleSucceed, + [], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); +} From 2ab11339460eb716bddbe161065a37176656faa9 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 21 Jun 2024 11:37:09 -0400 Subject: [PATCH 7/9] Convert takeSnapshot --- .../googlemaps/GoogleMapController.java | 41 +++++++++---------- .../flutter/plugins/googlemaps/Messages.java | 32 +++++++++++++++ .../lib/src/google_maps_flutter_android.dart | 2 +- .../lib/src/messages.g.dart | 30 ++++++++++++++ .../pigeons/messages.dart | 4 ++ .../google_maps_flutter_android_test.dart | 11 +++++ ...oogle_maps_flutter_android_test.mocks.dart | 12 ++++++ 7 files changed, 110 insertions(+), 22 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index d08be26f86a0..44757f674ca3 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -28,7 +28,6 @@ import androidx.lifecycle.LifecycleOwner; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.OnMapReadyCallback; @@ -318,26 +317,6 @@ public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) result.success(Convert.cameraPositionToJson(getCameraPosition())); break; } - case "map#takeSnapshot": - { - if (googleMap != null) { - final MethodChannel.Result _result = result; - googleMap.snapshot( - new SnapshotReadyCallback() { - @Override - public void onSnapshotReady(Bitmap bitmap) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - bitmap.recycle(); - _result.success(byteArray); - } - }); - } else { - result.error("GoogleMap uninitialized", "takeSnapshot", null); - } - break; - } case "camera#move": { final CameraUpdate cameraUpdate = @@ -1013,6 +992,26 @@ public Boolean isInfoWindowShown(@NonNull String markerId) { return lastSetStyleSucceeded; } + @Override + public void takeSnapshot(@NonNull Messages.Result result) { + if (googleMap == null) { + result.error(new FlutterError("GoogleMap uninitialized", "takeSnapshot", null)); + } else { + googleMap.snapshot( + bitmap -> { + if (bitmap == null) { + result.error(new FlutterError("Snapshot failure", "Unable to take snapshot", null)); + } else { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + byte[] byteArray = stream.toByteArray(); + bitmap.recycle(); + result.success(byteArray); + } + }); + } + } + /** MapsInspectorApi implementation */ @Override public @NonNull Boolean areBuildingsEnabled() { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index 1fcd216fd03d..d68c9edcb057 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -759,6 +759,8 @@ public interface MapsApi { */ @NonNull Boolean didLastStyleSucceed(); + /** Takes a snapshot of the map and returns its image data. */ + void takeSnapshot(@NonNull Result result); /** The codec used by MapsApi. */ static @NonNull MessageCodec getCodec() { @@ -1032,6 +1034,36 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.takeSnapshot" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + Result resultCallback = + new Result() { + public void success(byte[] result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.takeSnapshot(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** 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 f0ee83263b03..ff2541861fdc 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 @@ -532,7 +532,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { Future takeSnapshot({ required int mapId, }) { - return _channel(mapId).invokeMethod('map#takeSnapshot'); + return _hostApi(mapId).takeSnapshot(); } @override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index 45a3b785f684..12f5e046b716 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -558,6 +558,36 @@ class MapsApi { return (__pigeon_replyList[0] as bool?)!; } } + + /// Takes a snapshot of the map and returns its image data. + Future takeSnapshot() async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.takeSnapshot$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as Uint8List?)!; + } + } } /// Inspector API only intended for use in integration tests. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index 01cce38e22fb..ae7bf0a2bb70 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -120,6 +120,10 @@ abstract class MapsApi { /// This allows checking asynchronously for initial style failures, as there /// is no way to return failures from map initialization. bool didLastStyleSucceed(); + + /// Takes a snapshot of the map and returns its image data. + @async + Uint8List takeSnapshot(); } /// Inspector API only intended for use in integration tests. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index a20f7254ac0a..9c34e7a706f6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -161,6 +161,17 @@ void main() { true); }); + test('takeSnapshot calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + final Uint8List fakeSnapshot = Uint8List(10); + when(api.takeSnapshot()).thenAnswer((_) async => fakeSnapshot); + + expect(await maps.takeSnapshot(mapId: mapId), fakeSnapshot); + }); + test('markers send drag event to correct streams', () async { const int mapId = 1; final Map jsonMarkerDragStartEvent = { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart index 8628c96b46e6..590e97281555 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart @@ -4,6 +4,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; +import 'dart:typed_data' as _i4; import 'package:google_maps_flutter_android/src/messages.g.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; @@ -201,4 +202,15 @@ class MockMapsApi extends _i1.Mock implements _i2.MapsApi { returnValue: _i3.Future.value(false), returnValueForMissingStub: _i3.Future.value(false), ) as _i3.Future); + + @override + _i3.Future<_i4.Uint8List> takeSnapshot() => (super.noSuchMethod( + Invocation.method( + #takeSnapshot, + [], + ), + returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)), + returnValueForMissingStub: + _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)), + ) as _i3.Future<_i4.Uint8List>); } From 0911907d71f261efad15953e7176ec8a8ecc7045 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 21 Jun 2024 11:43:28 -0400 Subject: [PATCH 8/9] Convert clearTileCache --- .../googlemaps/GoogleMapController.java | 12 ++++---- .../flutter/plugins/googlemaps/Messages.java | 28 +++++++++++++++++++ .../lib/src/google_maps_flutter_android.dart | 5 +--- .../lib/src/messages.g.dart | 25 +++++++++++++++++ .../pigeons/messages.dart | 3 ++ .../google_maps_flutter_android_test.dart | 11 ++++++++ ...oogle_maps_flutter_android_test.mocks.dart | 10 +++++++ 7 files changed, 83 insertions(+), 11 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 44757f674ca3..e3eee3bc1a79 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -401,13 +401,6 @@ public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) result.success(null); break; } - case "tileOverlays#clearTileCache": - { - String tileOverlayId = call.argument("tileOverlayId"); - tileOverlaysController.clearTileCache(tileOverlayId); - result.success(null); - break; - } default: result.notImplemented(); } @@ -992,6 +985,11 @@ public Boolean isInfoWindowShown(@NonNull String markerId) { return lastSetStyleSucceeded; } + @Override + public void clearTileCache(@NonNull String tileOverlayId) { + tileOverlaysController.clearTileCache(tileOverlayId); + } + @Override public void takeSnapshot(@NonNull Messages.Result result) { if (googleMap == null) { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java index d68c9edcb057..5c537c9c3bf4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Messages.java @@ -759,6 +759,8 @@ public interface MapsApi { */ @NonNull Boolean didLastStyleSucceed(); + /** Clears the cache of tiles previously requseted from the tile provider. */ + void clearTileCache(@NonNull String tileOverlayId); /** Takes a snapshot of the map and returns its image data. */ void takeSnapshot(@NonNull Result result); @@ -1034,6 +1036,32 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.google_maps_flutter_android.MapsApi.clearTileCache" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String tileOverlayIdArg = (String) args.get(0); + try { + api.clearTileCache(tileOverlayIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( 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 ff2541861fdc..0664ae3accd5 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 @@ -433,10 +433,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { TileOverlayId tileOverlayId, { required int mapId, }) { - return _channel(mapId) - .invokeMethod('tileOverlays#clearTileCache', { - 'tileOverlayId': tileOverlayId.value, - }); + return _hostApi(mapId).clearTileCache(tileOverlayId.value); } @override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart index 12f5e046b716..fa7445f748fe 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/messages.g.dart @@ -559,6 +559,31 @@ class MapsApi { } } + /// Clears the cache of tiles previously requseted from the tile provider. + Future clearTileCache(String tileOverlayId) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.google_maps_flutter_android.MapsApi.clearTileCache$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([tileOverlayId]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + /// Takes a snapshot of the map and returns its image data. Future takeSnapshot() async { final String __pigeon_channelName = diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart index ae7bf0a2bb70..1a50204c5805 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/pigeons/messages.dart @@ -121,6 +121,9 @@ abstract class MapsApi { /// is no way to return failures from map initialization. bool didLastStyleSucceed(); + /// Clears the cache of tiles previously requseted from the tile provider. + void clearTileCache(String tileOverlayId); + /// Takes a snapshot of the map and returns its image data. @async Uint8List takeSnapshot(); diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 9c34e7a706f6..109b9411a32e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -172,6 +172,17 @@ void main() { expect(await maps.takeSnapshot(mapId: mapId), fakeSnapshot); }); + test('clearTileCache calls through', () async { + const int mapId = 1; + final (GoogleMapsFlutterAndroid maps, MockMapsApi api) = + setUpMockMap(mapId: mapId); + + const String tileOverlayId = 'overlay'; + await maps.clearTileCache(const TileOverlayId(tileOverlayId), mapId: mapId); + + verify(api.clearTileCache(tileOverlayId)); + }); + test('markers send drag event to correct streams', () async { const int mapId = 1; final Map jsonMarkerDragStartEvent = { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart index 590e97281555..f819660e2137 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.mocks.dart @@ -203,6 +203,16 @@ class MockMapsApi extends _i1.Mock implements _i2.MapsApi { returnValueForMissingStub: _i3.Future.value(false), ) as _i3.Future); + @override + _i3.Future clearTileCache(String? tileOverlayId) => (super.noSuchMethod( + Invocation.method( + #clearTileCache, + [tileOverlayId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override _i3.Future<_i4.Uint8List> takeSnapshot() => (super.noSuchMethod( Invocation.method( From 9be8e55492cedec301405c6ed3778ffe03ab8fb0 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 21 Jun 2024 12:38:31 -0400 Subject: [PATCH 9/9] Version bump --- .../google_maps_flutter_android/CHANGELOG.md | 4 ++++ .../example/android/app/build.gradle | 2 +- .../google_maps_flutter_android/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 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 24847dd1531c..2291ed28c475 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.10.0 + +* Converts some platform calls to Pigeon. + ## 2.9.1 * Converts inspector interface platform calls to Pigeon. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle index 96f5b50f286d..6ab57de08264 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle @@ -31,7 +31,7 @@ android { defaultConfig { applicationId "io.flutter.plugins.googlemapsexample" - minSdkVersion 20 + minSdkVersion flutter.minSdkVersion targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() 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 ff14604e0603..087914e6b3e5 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.9.1 +version: 2.10.0 environment: sdk: ^3.4.0