[google_maps_flutter] Cloud-based map styling support (#3682)
Recreates https://github.com/flutter/plugins/pull/6553 form flutter/plugins which had approvals in-progress.
Fixes https://github.com/flutter/flutter/issues/67631
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index 1e2f222..079355c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.5.0
+
+* Adds implementation for `cloudMapId` parameter to support cloud-based maps styling.
+
## 2.4.1
* Adds pub topics to package metadata.
diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md
index 6b89baa..b4b956f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter/README.md
@@ -61,6 +61,11 @@
[platform view display modes](https://flutter.dev/docs/development/platform-integration/platform-views).
For details, see [the Android README](https://pub.dev/packages/google_maps_flutter_android#display-mode).
+#### Cloud-based map styling
+
+Cloud-based map styling works on Android only if `AndroidMapRenderer.latest` map renderer has been initialized.
+For details, see [the Android README](https://pub.dev/packages/google_maps_flutter_android#map-renderer).
+
### iOS
To set up, specify your API key in the application delegate `ios/Runner/AppDelegate.m`:
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart
index 98ea6d9..1353c59 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart
@@ -407,6 +407,30 @@
// TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled.
// https://github.com/flutter/flutter/issues/57057
skip: isAndroid || isWeb);
+
+ testWidgets(
+ 'testCloudMapId',
+ (WidgetTester tester) async {
+ final Completer<int> mapIdCompleter = Completer<int>();
+ final Key key = GlobalKey();
+
+ await pumpMap(
+ tester,
+ GoogleMap(
+ key: key,
+ initialCameraPosition: kInitialCameraPosition,
+ onMapCreated: (GoogleMapController controller) {
+ mapIdCompleter.complete(controller.mapId);
+ },
+ cloudMapId: kCloudMapId,
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ // Await mapIdCompleter to finish to make sure map can be created with cloudMapId
+ await mapIdCompleter.future;
+ },
+ );
}
/// Repeatedly checks an asynchronous value against a test condition.
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart
index 013565a..126733f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart
@@ -19,6 +19,9 @@
const CameraPosition kInitialCameraPosition =
CameraPosition(target: kInitialMapCenter, zoom: kInitialZoomLevel);
+// Dummy map ID
+const String kCloudMapId = '000000000000000'; // Dummy map ID.
+
/// True if the test is running in an iOS device
final bool isIOS = defaultTargetPlatform == TargetPlatform.iOS;
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile b/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
index b690cc7..b6bc7de 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
+++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
@@ -1,5 +1,5 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '11.0'
+# Global platform version is set to 12 for this example project to support cloud-based maps styling
+platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
index 96479e3..a0060e1 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
+
import 'package:flutter/material.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';
@@ -10,6 +12,7 @@
import 'lite_mode.dart';
import 'map_click.dart';
import 'map_coordinates.dart';
+import 'map_map_id.dart';
import 'map_ui.dart';
import 'marker_icons.dart';
import 'move_camera.dart';
@@ -39,6 +42,7 @@
const SnapshotPage(),
const LiteModePage(),
const TileOverlayPage(),
+ const MapIdPage(),
];
/// MapsDemo is the Main Application.
@@ -75,6 +79,38 @@
GoogleMapsFlutterPlatform.instance;
if (mapsImplementation is GoogleMapsFlutterAndroid) {
mapsImplementation.useAndroidViewSurface = true;
+ initializeMapRenderer();
}
runApp(const MaterialApp(home: MapsDemo()));
}
+
+Completer<AndroidMapRenderer?>? _initializedRendererCompleter;
+
+/// Initializes map renderer to the `latest` renderer type for Android platform.
+///
+/// The renderer must be requested before creating GoogleMap instances,
+/// as the renderer can be initialized only once per application context.
+Future<AndroidMapRenderer?> initializeMapRenderer() async {
+ if (_initializedRendererCompleter != null) {
+ return _initializedRendererCompleter!.future;
+ }
+
+ final Completer<AndroidMapRenderer?> completer =
+ Completer<AndroidMapRenderer?>();
+ _initializedRendererCompleter = completer;
+
+ WidgetsFlutterBinding.ensureInitialized();
+
+ final GoogleMapsFlutterPlatform mapsImplementation =
+ GoogleMapsFlutterPlatform.instance;
+ if (mapsImplementation is GoogleMapsFlutterAndroid) {
+ unawaited(mapsImplementation
+ .initializeWithRenderer(AndroidMapRenderer.latest)
+ .then((AndroidMapRenderer initializedRenderer) =>
+ completer.complete(initializedRenderer)));
+ } else {
+ completer.complete(null);
+ }
+
+ return completer.future;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_map_id.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_map_id.dart
new file mode 100644
index 0000000..6b90d77
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_map_id.dart
@@ -0,0 +1,140 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:io';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
+import 'main.dart';
+import 'page.dart';
+
+class MapIdPage extends GoogleMapExampleAppPage {
+ const MapIdPage({Key? key})
+ : super(const Icon(Icons.map), 'Cloud-based maps styling', key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return const MapIdBody();
+ }
+}
+
+class MapIdBody extends StatefulWidget {
+ const MapIdBody({super.key});
+
+ @override
+ State<StatefulWidget> createState() => MapIdBodyState();
+}
+
+const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
+
+class MapIdBodyState extends State<MapIdBody> {
+ GoogleMapController? controller;
+
+ Key _key = const Key('mapId#');
+ String? _mapId;
+ final TextEditingController _mapIdController = TextEditingController();
+ AndroidMapRenderer? _initializedRenderer;
+
+ @override
+ void initState() {
+ initializeMapRenderer()
+ .then<void>((AndroidMapRenderer? initializedRenderer) => setState(() {
+ _initializedRenderer = initializedRenderer;
+ }));
+ super.initState();
+ }
+
+ String _getInitializedsRendererType() {
+ switch (_initializedRenderer) {
+ case AndroidMapRenderer.latest:
+ return 'latest';
+ case AndroidMapRenderer.legacy:
+ return 'legacy';
+ case AndroidMapRenderer.platformDefault:
+ case null:
+ break;
+ }
+ return 'unknown';
+ }
+
+ void _setMapId() {
+ setState(() {
+ _mapId = _mapIdController.text;
+
+ // Change key to initialize new map instance for new mapId.
+ _key = Key(_mapId ?? 'mapId#');
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final GoogleMap googleMap = GoogleMap(
+ onMapCreated: _onMapCreated,
+ initialCameraPosition: const CameraPosition(
+ target: _kMapCenter,
+ zoom: 7.0,
+ ),
+ key: _key,
+ cloudMapId: _mapId);
+
+ final List<Widget> columnChildren = <Widget>[
+ Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: Center(
+ child: SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: googleMap,
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: TextField(
+ controller: _mapIdController,
+ decoration: const InputDecoration(
+ hintText: 'Map Id',
+ ),
+ )),
+ Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: ElevatedButton(
+ onPressed: () => _setMapId(),
+ child: const Text(
+ 'Press to use specified map Id',
+ ),
+ )),
+ if (!kIsWeb &&
+ Platform.isAndroid &&
+ _initializedRenderer != AndroidMapRenderer.latest)
+ Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: Text(
+ 'On Android, Cloud-based maps styling only works with "latest" renderer.\n\n'
+ 'Current initialized renderer is "${_getInitializedsRendererType()}".'),
+ ),
+ ];
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: columnChildren,
+ );
+ }
+
+ @override
+ void dispose() {
+ _mapIdController.dispose();
+ super.dispose();
+ }
+
+ void _onMapCreated(GoogleMapController controllerParam) {
+ setState(() {
+ controller = controllerParam;
+ });
+ }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
index 3c90433..cced821 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml
@@ -18,7 +18,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- google_maps_flutter_android: ^2.1.10
+ google_maps_flutter_android: ^2.5.0
google_maps_flutter_platform_interface: ^2.4.0
dev_dependencies:
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
index 14fe651..03adf3a 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
@@ -125,6 +125,7 @@
this.onCameraIdle,
this.onTap,
this.onLongPress,
+ this.cloudMapId,
});
/// Callback method for when the map is ready to be used.
@@ -292,6 +293,12 @@
/// See [WebGestureHandling] for more details.
final WebGestureHandling? webGestureHandling;
+ /// Identifier that's associated with a specific cloud-based map style.
+ ///
+ /// See https://developers.google.com/maps/documentation/get-map-id
+ /// for more details.
+ final String? cloudMapId;
+
/// Creates a [State] for this [GoogleMap].
@override
State createState() => _GoogleMapState();
@@ -548,5 +555,6 @@
indoorViewEnabled: map.indoorViewEnabled,
trafficEnabled: map.trafficEnabled,
buildingsEnabled: map.buildingsEnabled,
+ cloudMapId: map.cloudMapId,
);
}
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index 9fa3fe7..c5dbbb3 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 2.4.1
+version: 2.5.0
environment:
sdk: ">=3.0.0 <4.0.0"
@@ -21,8 +21,8 @@
dependencies:
flutter:
sdk: flutter
- google_maps_flutter_android: ^2.1.10
- google_maps_flutter_ios: ^2.1.10
+ google_maps_flutter_android: ^2.5.0
+ google_maps_flutter_ios: ^2.3.0
google_maps_flutter_platform_interface: ^2.4.0
google_maps_flutter_web: ^0.5.2