From 930318a82735042d5dd0d9028a2e66826aaa4589 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 28 Feb 2024 15:06:05 -0500 Subject: [PATCH] [google_maps_flutter] Add `style` to widget (#6192) Adds `style` to the `GoogleMap` widget construction, and internally to the `MapConfiguration` structure, wiring updates to style through the existing configuration update system instead of needing to be set via a separate channel. This makes things more consistent (style is conceptually similar to many of the other configuration options, and is not something that's likely to require frequent dynamic updates), and more importantly allows setting the style as part of creation instead of asynchronously after the map has likely already been displayed. Because of https://github.com/flutter/flutter/issues/24327, this adds a way to query for style errors after the fact. (It's possible that it's why `setMapStyle` was done that way in the first place; the initial PR doesn't explain.) While this is slightly clunky, it's unlikely that this method would actually be used in production code anyway, since presumably people are defining their styles at build time, not runtime, and thus probably don't need runtime error handling. Long term, the solution to this problem is likely to invert the widget/controller relationship, as was done in webview_flutter, but that's a major breaking change and not something we're ready to take on at this point. Since we don't really want two recommended ways of doing the same thing, this deprecates the existing `setMapStyle` method in favor of the new approach. Fixes https://github.com/flutter/flutter/issues/66207 --- .../google_maps_flutter/CHANGELOG.md | 11 ++++-- .../integration_test/src/maps_controller.dart | 22 ++++++++++++ .../example/lib/map_ui.dart | 34 ++++++++++--------- .../google_maps_flutter/example/pubspec.yaml | 2 +- .../lib/src/controller.dart | 6 ++++ .../lib/src/google_map.dart | 17 ++++++++++ .../google_maps_flutter/pubspec.yaml | 14 ++++---- .../test/google_map_test.dart | 28 +++++++++++++++ 8 files changed, 107 insertions(+), 27 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 095e04709027..50ca4a9e6894 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,11 @@ -## NEXT - -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +## 2.6.0 + +* Adds `style` to the GoogleMap widget constructor. This allows setting the map + style during creation, avoiding the possibility of the default style being + displayed briefly. +* Deprecates `GoogleMapController.setMapStyle` in favor of setting the style via + the new widget `style` parameter. +* Updates minimum supported SDK version to Flutter 3.19. ## 2.5.3 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 01716232814e..3d99369e33be 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 @@ -448,6 +448,28 @@ void runTests() { await mapIdCompleter.future; }, ); + + testWidgets('getStyleError reports last error', (WidgetTester tester) async { + final Key key = GlobalKey(); + final Completer controllerCompleter = + Completer(); + + await pumpMap( + tester, + GoogleMap( + key: key, + initialCameraPosition: kInitialCameraPosition, + style: '[[[this is an invalid style', + onMapCreated: (GoogleMapController controller) { + controllerCompleter.complete(controller); + }, + ), + ); + + final GoogleMapController controller = await controllerCompleter.future; + final String? error = await controller.getStyleError(); + expect(error, isNotNull); + }); } /// Repeatedly checks an asynchronous value against a test condition. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart index 5348c1001af7..26c0b2ab720a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart @@ -4,6 +4,7 @@ // ignore_for_file: public_member_api_docs +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -59,6 +60,7 @@ class MapUiBodyState extends State { bool _myLocationButtonEnabled = true; late GoogleMapController _controller; bool _nightMode = false; + String _mapStyle = ''; @override void initState() { @@ -243,27 +245,18 @@ class MapUiBodyState extends State { return rootBundle.loadString(path); } - void _setMapStyle(String mapStyle) { - setState(() { - _nightMode = true; - _controller.setMapStyle(mapStyle); - }); - } - // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), - onPressed: () { - if (_nightMode) { - setState(() { - _nightMode = false; - _controller.setMapStyle(null); - }); - } else { - _getFileData('assets/night_mode.json').then(_setMapStyle); - } + onPressed: () async { + _nightMode = !_nightMode; + final String style = + _nightMode ? await _getFileData('assets/night_mode.json') : ''; + setState(() { + _mapStyle = style; + }); }, ); } @@ -278,6 +271,7 @@ class MapUiBodyState extends State { cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, + style: _mapStyle, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, @@ -352,5 +346,13 @@ class MapUiBodyState extends State { _controller = controller; _isMapCreated = true; }); + // Log any style errors to the console for debugging. + if (kDebugMode) { + _controller.getStyleError().then((String? error) { + if (error != null) { + debugPrint(error); + } + }); + } } } 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 efed14ceecbf..ba306fd8bd9e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ google_maps_flutter_android: ^2.5.0 - google_maps_flutter_platform_interface: ^2.4.0 + google_maps_flutter_platform_interface: ^2.5.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index 095d15867acc..393288a6165f 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -190,11 +190,17 @@ class GoogleMapController { /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) /// style reference for more information regarding the supported styles. + @Deprecated('Use GoogleMap.style instead.') Future setMapStyle(String? mapStyle) { return GoogleMapsFlutterPlatform.instance .setMapStyle(mapStyle, mapId: mapId); } + /// Returns the last style error, if any. + Future getStyleError() { + return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId); + } + /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); 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 3b73e55664dd..c50ffccfa634 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 @@ -91,6 +91,7 @@ class GoogleMap extends StatefulWidget { const GoogleMap({ super.key, required this.initialCameraPosition, + this.style, this.onMapCreated, this.gestureRecognizers = const >{}, this.webGestureHandling, @@ -136,6 +137,19 @@ class GoogleMap extends StatefulWidget { /// The initial position of the map's camera. final CameraPosition initialCameraPosition; + /// The style for the map. + /// + /// Set to null to clear any previous custom styling. + /// + /// If problems were detected with the [mapStyle], including un-parsable + /// styling JSON, unrecognized feature type, unrecognized element type, or + /// invalid styler keys, the style is left unchanged, and the error can be + /// retrieved with [GoogleMapController.getStyleError]. + /// + /// The style string can be generated using the + /// [map style tool](https://mapstyle.withgoogle.com/). + final String? style; + /// True if the map should show a compass when rotated. final bool compassEnabled; @@ -556,5 +570,8 @@ MapConfiguration _configurationFromMapWidget(GoogleMap map) { trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, cloudMapId: map.cloudMapId, + // A null style in the widget means no style, which is expressed as '' in + // the configuration to distinguish from no change (null). + style: map.style ?? '', ); } diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 9b53f596fdf6..598ab6f7aeb6 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,11 +2,11 @@ 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.5.3 +version: 2.6.0 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ^3.3.0 + flutter: ">=3.19.0" flutter: plugin: @@ -21,10 +21,10 @@ flutter: dependencies: flutter: sdk: flutter - 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 + google_maps_flutter_android: ^2.7.0 + google_maps_flutter_ios: ^2.5.0 + google_maps_flutter_platform_interface: ^2.5.0 + google_maps_flutter_web: ^0.5.6 dev_dependencies: flutter_test: diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart index 7005a8d3ab60..9276a7dbda30 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart @@ -551,4 +551,32 @@ void main() { expect(map.mapConfiguration.buildingsEnabled, true); }); + + testWidgets('Can update style', (WidgetTester tester) async { + const String initialStyle = '[]'; + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), + style: initialStyle, + ), + ), + ); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.mapConfiguration.style, initialStyle); + + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: GoogleMap( + initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), + ), + ), + ); + + expect(map.mapConfiguration.style, ''); + }); }