[google_maps_flutter] Default Android to Hybrid Composition (#6334)
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 91f01f8a..01c98f3 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
@@ -1,5 +1,7 @@
-## NEXT
+## 2.3.0
+* Switches the default for `useAndroidViewSurface` to true, and adds
+ information about the current mode behaviors to the README.
* Updates minimum Flutter version to 2.10.
## 2.2.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/README.md b/packages/google_maps_flutter/google_maps_flutter_android/README.md
index 5ac1df0..877b9bb 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter_android/README.md
@@ -1,5 +1,7 @@
# google\_maps\_flutter\_android
+<?code-excerpt path-base="excerpts/packages/google_maps_flutter_example"?>
+
The Android implementation of [`google_maps_flutter`][1].
## Usage
@@ -8,5 +10,45 @@
`google_maps_flutter` normally. This package will be automatically included in
your app when you do.
+## Display Mode
+
+This plugin supports two different [platform view display modes][3]. The default
+display mode is subject to change in the future, and will not be considered a
+breaking change, so if you want to ensure a specific mode you can set it
+explicitly:
+
+<?code-excerpt "readme_excerpts.dart (DisplayMode)"?>
+```dart
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+ // Require Hybrid Composition mode on Android.
+ final GoogleMapsFlutterPlatform mapsImplementation =
+ GoogleMapsFlutterPlatform.instance;
+ if (mapsImplementation is GoogleMapsFlutterAndroid) {
+ mapsImplementation.useAndroidViewSurface = true;
+ }
+ // ···
+}
+```
+
+### Hybrid Composition
+
+This is the current default mode, and corresponds to
+`useAndroidViewSurface = true`. It ensures that the map display will work as
+expected, at the cost of some performance.
+
+### Texture Layer Hybrid Composition
+
+This is a new display mode used by most plugins starting with Flutter 3.0, and
+corresponds to `useAndroidViewSurface = false`. This is more performant than
+Hybrid Composition, but currently [misses certain map updates][4].
+
+This mode will likely become the default in future versions if/when the
+missed updates issue can be resolved.
+
[1]: https://pub.dev/packages/google_maps_flutter
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
+[3]: https://docs.flutter.dev/development/platform-integration/android/platform-views
+[4]: https://github.com/flutter/flutter/issues/103686
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml
new file mode 100644
index 0000000..e317efa
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml
@@ -0,0 +1,15 @@
+targets:
+ $default:
+ sources:
+ include:
+ - lib/**
+ # Some default includes that aren't really used here but will prevent
+ # false-negative warnings:
+ - $package$
+ - lib/$lib$
+ exclude:
+ - '**/.*/**'
+ - '**/build/**'
+ builders:
+ code_excerpter|code_excerpter:
+ enabled: true
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
index 8fc1ede..0945740 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
@@ -23,6 +23,28 @@
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
+ // Repeatedly checks an asynchronous value against a test condition, waiting
+ // on frame between each check, returing the value if it passes the predicate
+ // before [maxTries] is reached.
+ //
+ // Returns null if the predicate is never satisfied.
+ //
+ // This is useful for cases where the Maps SDK has some internally
+ // asynchronous operation that we don't have visibility into (e.g., native UI
+ // animations).
+ Future<T?> waitForValueMatchingPredicate<T>(WidgetTester tester,
+ Future<T> Function() getValue, bool Function(T) predicate,
+ {int maxTries = 100}) async {
+ for (int i = 0; i < maxTries; i++) {
+ final T value = await getValue();
+ if (predicate(value)) {
+ return value;
+ }
+ await tester.pump();
+ }
+ return null;
+ }
+
testWidgets('uses surface view', (WidgetTester tester) async {
final GoogleMapsFlutterAndroid instance =
GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid;
@@ -484,12 +506,13 @@
final ExampleGoogleMapController mapController =
await mapControllerCompleter.future;
+ // Wait for the visible region to be non-zero.
final LatLngBounds firstVisibleRegion =
- await mapController.getVisibleRegion();
-
- expect(firstVisibleRegion, isNotNull);
- expect(firstVisibleRegion.southwest, isNotNull);
- expect(firstVisibleRegion.northeast, isNotNull);
+ await waitForValueMatchingPredicate<LatLngBounds>(
+ tester,
+ () => mapController.getVisibleRegion(),
+ (LatLngBounds bounds) => bounds != zeroLatLngBounds) ??
+ zeroLatLngBounds;
expect(firstVisibleRegion, isNot(zeroLatLngBounds));
expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue);
@@ -520,9 +543,6 @@
final LatLngBounds secondVisibleRegion =
await mapController.getVisibleRegion();
- expect(secondVisibleRegion, isNotNull);
- expect(secondVisibleRegion.southwest, isNotNull);
- expect(secondVisibleRegion.northeast, isNotNull);
expect(secondVisibleRegion, isNot(zeroLatLngBounds));
expect(firstVisibleRegion, isNot(secondVisibleRegion));
@@ -922,7 +942,13 @@
expect(iwVisibleStatus, false);
await controller.showMarkerInfoWindow(marker.markerId);
- iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
+ // The Maps SDK doesn't always return true for whether it is shown
+ // immediately after showing it, so wait for it to report as shown.
+ iwVisibleStatus = await waitForValueMatchingPredicate<bool>(
+ tester,
+ () => controller.isMarkerInfoWindowShown(marker.markerId),
+ (bool visible) => visible) ??
+ false;
expect(iwVisibleStatus, true);
await controller.hideMarkerInfoWindow(marker.markerId);
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart
new file mode 100644
index 0000000..5911c06
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart
@@ -0,0 +1,21 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+// #docregion DisplayMode
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+ // Require Hybrid Composition mode on Android.
+ final GoogleMapsFlutterPlatform mapsImplementation =
+ GoogleMapsFlutterPlatform.instance;
+ if (mapsImplementation is GoogleMapsFlutterAndroid) {
+ mapsImplementation.useAndroidViewSurface = true;
+ }
+ // #enddocregion DisplayMode
+ runApp(const MaterialApp());
+ // #docregion DisplayMode
+}
+// #enddocregion DisplayMode
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml
index 778af9a..cd71230 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml
@@ -21,6 +21,7 @@
google_maps_flutter_platform_interface: ^2.2.1
dev_dependencies:
+ build_runner: ^2.1.10
espresso: ^0.1.0+2
flutter_driver:
sdk: flutter
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 95dea4c..06c5bdc 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
@@ -471,19 +471,14 @@
return _channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
}
- /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget.
+ /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the
+ /// Google Maps widget.
///
- /// This implementation uses hybrid composition to render the Google Maps
- /// Widget on Android. This comes at the cost of some performance on Android
- /// versions below 10. See
- /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
- /// information.
+ /// See https://pub.dev/packages/google_maps_flutter_android#display-mode
+ /// for more information.
///
- /// If set to true, the google map widget should be built with
- /// [buildViewWithTextDirection] instead of [buildView].
- ///
- /// Defaults to false.
- bool useAndroidViewSurface = false;
+ /// Currently defaults to true, but the default is subject to change.
+ bool useAndroidViewSurface = true;
Widget _buildView(
int creationId,
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 2ee7c82..c820f31 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 @@
description: Android implementation of the google_maps_flutter plugin.
repository: https://github.com/flutter/plugins/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.2.0
+version: 2.3.0
environment:
sdk: ">=2.14.0 <3.0.0"
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 cba23d0..431c247 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
@@ -124,9 +124,10 @@
});
test(
- 'Default widget is AndroidView',
+ 'Does not use PlatformViewLink when using TLHC',
() async {
final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+ maps.useAndroidViewSurface = false;
final Widget widget = maps.buildViewWithConfiguration(1, (int _) {},
widgetConfiguration: const MapWidgetConfiguration(
initialCameraPosition:
@@ -150,4 +151,16 @@
expect(widget, isA<PlatformViewLink>());
});
+
+ testWidgets('Defaults to surface view', (WidgetTester tester) async {
+ final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+
+ final Widget widget = maps.buildViewWithConfiguration(1, (int _) {},
+ widgetConfiguration: const MapWidgetConfiguration(
+ initialCameraPosition:
+ CameraPosition(target: LatLng(0, 0), zoom: 1),
+ textDirection: TextDirection.ltr));
+
+ expect(widget, isA<PlatformViewLink>());
+ });
}