diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 1e2f222b34c..079355c7eb7 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 6b89baacd4c..b4b956f754d 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 @@ The Android implementation supports multiple [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 98ea6d932b2..1353c594490 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 @@ void runTests() { // 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 mapIdCompleter = Completer(); + 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 013565a2fdc..126733f678e 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 double kInitialZoomLevel = 5; 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 b690cc71379..b6bc7dedde1 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 96479e38e71..a0060e1c725 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 'animate_camera.dart'; 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 @@ final List _allPages = [ const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), + const MapIdPage(), ]; /// MapsDemo is the Main Application. @@ -75,6 +79,38 @@ void main() { GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { mapsImplementation.useAndroidViewSurface = true; + initializeMapRenderer(); } runApp(const MaterialApp(home: MapsDemo())); } + +Completer? _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 initializeMapRenderer() async { + if (_initializedRendererCompleter != null) { + return _initializedRendererCompleter!.future; + } + + final Completer completer = + Completer(); + _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 00000000000..6b90d777da0 --- /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 createState() => MapIdBodyState(); +} + +const LatLng _kMapCenter = LatLng(52.4478, -3.5402); + +class MapIdBodyState extends State { + GoogleMapController? controller; + + Key _key = const Key('mapId#'); + String? _mapId; + final TextEditingController _mapIdController = TextEditingController(); + AndroidMapRenderer? _initializedRenderer; + + @override + void initState() { + initializeMapRenderer() + .then((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 columnChildren = [ + 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 3c90433879d..cced821dd62 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 @@ dependencies: # 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 14fe651d0f6..03adf3aa47c 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 @@ class GoogleMap extends StatefulWidget { this.onCameraIdle, this.onTap, this.onLongPress, + this.cloudMapId, }); /// Callback method for when the map is ready to be used. @@ -292,6 +293,12 @@ class GoogleMap extends StatefulWidget { /// 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 @@ MapConfiguration _configurationFromMapWidget(GoogleMap map) { 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 9fa3fe7346c..c5dbbb3f955 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 @@ name: google_maps_flutter 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 @@ flutter: 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