Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[google_maps_flutter] Default Android to Hybrid Composition #6334

Merged
merged 4 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,5 +10,45 @@ This package is [endorsed][2], which means you can simply use
`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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ void main() {
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;
Expand Down Expand Up @@ -484,12 +506,13 @@ void main() {
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);

Expand Down Expand Up @@ -520,9 +543,6 @@ void main() {
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));
Expand Down Expand Up @@ -922,7 +942,13 @@ void main() {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +17 to +19
Copy link
Contributor

@bparrishMines bparrishMines Sep 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this just cuts out the runApp line from the doc region? I didn't know you could do that. Took me a minute to understand it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, code-excerpts lets you make non-contiguous pulls, and adds (by default) a // ... (or whatever makes sense for the language) in the generated output.

}
// #enddocregion DisplayMode
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies:
google_maps_flutter_platform_interface: ^2.2.1

dev_dependencies:
build_runner: ^2.1.10
espresso: ^0.1.0+2
flutter_driver:
sdk: flutter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,19 +471,14 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_maps_flutter_android
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ void main() {
});

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:
Expand All @@ -150,4 +151,16 @@ void main() {

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>());
});
}