Skip to content

Commit 3214a67

Browse files
[google_map_flutter] Add style to widget - platform impls (#6205)
Platform implementations portion of #6192 Adds handling of `MapConfiguration.style` and implements new `getStyleError` method. Part of flutter/flutter#66207
1 parent 7cdcf30 commit 3214a67

30 files changed

+426
-92
lines changed

packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
## NEXT
1+
## 2.7.0
22

3+
* Adds support for `MapConfiguration.style`.
4+
* Adds support for `getStyleError`.
35
* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1.
46
* Updates compileSdk version to 34.
57

packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java

+4
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ static void interpretGoogleMapOptions(Object o, GoogleMapOptionsSink sink) {
377377
if (buildingsEnabled != null) {
378378
sink.setBuildingsEnabled(toBoolean(buildingsEnabled));
379379
}
380+
final Object style = data.get("style");
381+
if (style != null) {
382+
sink.setMapStyle(toString(style));
383+
}
380384
}
381385

382386
/** Returns the dartMarkerId of the interpreted marker. */

packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import android.content.Context;
88
import android.graphics.Rect;
9+
import androidx.annotation.Nullable;
910
import com.google.android.gms.maps.GoogleMapOptions;
1011
import com.google.android.gms.maps.model.CameraPosition;
1112
import com.google.android.gms.maps.model.LatLngBounds;
@@ -27,6 +28,7 @@ class GoogleMapBuilder implements GoogleMapOptionsSink {
2728
private Object initialCircles;
2829
private List<Map<String, ?>> initialTileOverlays;
2930
private Rect padding = new Rect(0, 0, 0, 0);
31+
private @Nullable String style;
3032

3133
GoogleMapController build(
3234
int id,
@@ -48,6 +50,7 @@ GoogleMapController build(
4850
controller.setInitialCircles(initialCircles);
4951
controller.setPadding(padding.top, padding.left, padding.bottom, padding.right);
5052
controller.setInitialTileOverlays(initialTileOverlays);
53+
controller.setMapStyle(style);
5154
return controller;
5255
}
5356

@@ -178,4 +181,9 @@ public void setInitialCircles(Object initialCircles) {
178181
public void setInitialTileOverlays(List<Map<String, ?>> initialTileOverlays) {
179182
this.initialTileOverlays = initialTileOverlays;
180183
}
184+
185+
@Override
186+
public void setMapStyle(@Nullable String style) {
187+
this.style = style;
188+
}
181189
}

packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java

+35-13
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.util.HashMap;
4949
import java.util.List;
5050
import java.util.Map;
51+
import java.util.Objects;
5152

5253
/** Controller of a single GoogleMaps MapView instance. */
5354
final class GoogleMapController
@@ -87,6 +88,9 @@ final class GoogleMapController
8788
private List<Object> initialPolylines;
8889
private List<Object> initialCircles;
8990
private List<Map<String, ?>> initialTileOverlays;
91+
// Null except between initialization and onMapReady.
92+
private @Nullable String initialMapStyle;
93+
private @Nullable String lastStyleError;
9094
@VisibleForTesting List<Float> initialPadding;
9195

9296
GoogleMapController(
@@ -169,6 +173,10 @@ public void onMapReady(GoogleMap googleMap) {
169173
initialPadding.get(2),
170174
initialPadding.get(3));
171175
}
176+
if (initialMapStyle != null) {
177+
updateMapStyle(initialMapStyle);
178+
initialMapStyle = null;
179+
}
172180
}
173181

174182
// Returns the first TextureView found in the view hierarchy.
@@ -459,26 +467,22 @@ public void onSnapshotReady(Bitmap bitmap) {
459467
}
460468
case "map#setStyle":
461469
{
462-
boolean mapStyleSet;
463-
if (call.arguments instanceof String) {
464-
String mapStyle = (String) call.arguments;
465-
if (mapStyle == null) {
466-
mapStyleSet = googleMap.setMapStyle(null);
467-
} else {
468-
mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle));
469-
}
470-
} else {
471-
mapStyleSet = googleMap.setMapStyle(null);
472-
}
470+
Object arg = call.arguments;
471+
final String style = arg instanceof String ? (String) arg : null;
472+
final boolean mapStyleSet = updateMapStyle(style);
473473
ArrayList<Object> mapStyleResult = new ArrayList<>(2);
474474
mapStyleResult.add(mapStyleSet);
475475
if (!mapStyleSet) {
476-
mapStyleResult.add(
477-
"Unable to set the map style. Please check console logs for errors.");
476+
mapStyleResult.add(lastStyleError);
478477
}
479478
result.success(mapStyleResult);
480479
break;
481480
}
481+
case "map#getStyleError":
482+
{
483+
result.success(lastStyleError);
484+
break;
485+
}
482486
case "tileOverlays#update":
483487
{
484488
List<Map<String, ?>> tileOverlaysToAdd = call.argument("tileOverlaysToAdd");
@@ -926,4 +930,22 @@ public void setTrafficEnabled(boolean trafficEnabled) {
926930
public void setBuildingsEnabled(boolean buildingsEnabled) {
927931
this.buildingsEnabled = buildingsEnabled;
928932
}
933+
934+
public void setMapStyle(@Nullable String style) {
935+
if (googleMap == null) {
936+
initialMapStyle = style;
937+
} else {
938+
updateMapStyle(style);
939+
}
940+
}
941+
942+
private boolean updateMapStyle(String style) {
943+
// Dart passes an empty string to indicate that the style should be cleared.
944+
final MapStyleOptions mapStyleOptions =
945+
style == null || style.isEmpty() ? null : new MapStyleOptions(style);
946+
final boolean set = Objects.requireNonNull(googleMap).setMapStyle(mapStyleOptions);
947+
lastStyleError =
948+
set ? null : "Unable to set the map style. Please check console logs for errors.";
949+
return set;
950+
}
929951
}

packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.flutter.plugins.googlemaps;
66

7+
import androidx.annotation.Nullable;
78
import com.google.android.gms.maps.model.LatLngBounds;
89
import java.util.List;
910
import java.util.Map;
@@ -55,4 +56,6 @@ interface GoogleMapOptionsSink {
5556
void setInitialCircles(Object initialCircles);
5657

5758
void setInitialTileOverlays(List<Map<String, ?>> initialTileOverlays);
59+
60+
void setMapStyle(@Nullable String style);
5861
}

packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart

+59-3
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,8 @@ void googleMapsTests() {
722722
await controllerCompleter.future;
723723
const String mapStyle =
724724
'[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]';
725-
await controller.setMapStyle(mapStyle);
725+
await GoogleMapsFlutterPlatform.instance
726+
.setMapStyle(mapStyle, mapId: controller.mapId);
726727
});
727728

728729
testWidgets('testSetMapStyle invalid Json String',
@@ -746,10 +747,12 @@ void googleMapsTests() {
746747
await controllerCompleter.future;
747748

748749
try {
749-
await controller.setMapStyle('invalid_value');
750+
await GoogleMapsFlutterPlatform.instance
751+
.setMapStyle('invalid_value', mapId: controller.mapId);
750752
fail('expected MapStyleException');
751753
} on MapStyleException catch (e) {
752754
expect(e.cause, isNotNull);
755+
expect(await controller.getStyleError(), isNotNull);
753756
}
754757
});
755758

@@ -771,7 +774,8 @@ void googleMapsTests() {
771774

772775
final ExampleGoogleMapController controller =
773776
await controllerCompleter.future;
774-
await controller.setMapStyle(null);
777+
await GoogleMapsFlutterPlatform.instance
778+
.setMapStyle(null, mapId: controller.mapId);
775779
});
776780

777781
testWidgets('testGetLatLng', (WidgetTester tester) async {
@@ -1211,6 +1215,58 @@ void googleMapsTests() {
12111215
await mapIdCompleter.future;
12121216
},
12131217
);
1218+
1219+
testWidgets('getStyleError reports last error', (WidgetTester tester) async {
1220+
final Key key = GlobalKey();
1221+
final Completer<ExampleGoogleMapController> controllerCompleter =
1222+
Completer<ExampleGoogleMapController>();
1223+
1224+
await tester.pumpWidget(Directionality(
1225+
textDirection: TextDirection.ltr,
1226+
child: ExampleGoogleMap(
1227+
key: key,
1228+
initialCameraPosition: _kInitialCameraPosition,
1229+
style: '[[[this is an invalid style',
1230+
onMapCreated: (ExampleGoogleMapController controller) {
1231+
controllerCompleter.complete(controller);
1232+
},
1233+
),
1234+
));
1235+
1236+
final ExampleGoogleMapController controller =
1237+
await controllerCompleter.future;
1238+
String? error = await controller.getStyleError();
1239+
for (int i = 0; i < 1000 && error == null; i++) {
1240+
await Future<void>.delayed(const Duration(milliseconds: 10));
1241+
error = await controller.getStyleError();
1242+
}
1243+
expect(error, isNotNull);
1244+
});
1245+
1246+
testWidgets('getStyleError returns null for a valid style',
1247+
(WidgetTester tester) async {
1248+
final Key key = GlobalKey();
1249+
final Completer<ExampleGoogleMapController> controllerCompleter =
1250+
Completer<ExampleGoogleMapController>();
1251+
1252+
await tester.pumpWidget(Directionality(
1253+
textDirection: TextDirection.ltr,
1254+
child: ExampleGoogleMap(
1255+
key: key,
1256+
initialCameraPosition: _kInitialCameraPosition,
1257+
// An empty array is the simplest valid style.
1258+
style: '[]',
1259+
onMapCreated: (ExampleGoogleMapController controller) {
1260+
controllerCompleter.complete(controller);
1261+
},
1262+
),
1263+
));
1264+
1265+
final ExampleGoogleMapController controller =
1266+
await controllerCompleter.future;
1267+
final String? error = await controller.getStyleError();
1268+
expect(error, isNull);
1269+
});
12141270
}
12151271

12161272
class _DebugTileProvider implements TileProvider {

packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart

+10-6
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,6 @@ class ExampleGoogleMapController {
144144
.moveCamera(cameraUpdate, mapId: mapId);
145145
}
146146

147-
/// Sets the styling of the base map.
148-
Future<void> setMapStyle(String? mapStyle) {
149-
return GoogleMapsFlutterPlatform.instance
150-
.setMapStyle(mapStyle, mapId: mapId);
151-
}
152-
153147
/// Return [LatLngBounds] defining the region that is visible in a map.
154148
Future<LatLngBounds> getVisibleRegion() {
155149
return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId);
@@ -195,6 +189,11 @@ class ExampleGoogleMapController {
195189
return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId);
196190
}
197191

192+
/// Returns the last style error, if any.
193+
Future<String?> getStyleError() {
194+
return GoogleMapsFlutterPlatform.instance.getStyleError(mapId: mapId);
195+
}
196+
198197
/// Disposes of the platform resources
199198
void dispose() {
200199
GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId);
@@ -245,6 +244,7 @@ class ExampleGoogleMap extends StatefulWidget {
245244
this.onTap,
246245
this.onLongPress,
247246
this.cloudMapId,
247+
this.style,
248248
});
249249

250250
/// Callback method for when the map is ready to be used.
@@ -353,6 +353,9 @@ class ExampleGoogleMap extends StatefulWidget {
353353
/// for more details.
354354
final String? cloudMapId;
355355

356+
/// The locally configured style for the map.
357+
final String? style;
358+
356359
/// Creates a [State] for this [ExampleGoogleMap].
357360
@override
358361
State createState() => _ExampleGoogleMapState();
@@ -539,5 +542,6 @@ MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
539542
trafficEnabled: map.trafficEnabled,
540543
buildingsEnabled: map.buildingsEnabled,
541544
cloudMapId: map.cloudMapId,
545+
style: map.style,
542546
);
543547
}

packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart

+15-18
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class MapUiBodyState extends State<MapUiBody> {
6060
bool _myLocationButtonEnabled = true;
6161
late ExampleGoogleMapController _controller;
6262
bool _nightMode = false;
63+
String _mapStyle = '';
6364

6465
@override
6566
void initState() {
@@ -244,27 +245,16 @@ class MapUiBodyState extends State<MapUiBody> {
244245
return rootBundle.loadString(path);
245246
}
246247

247-
void _setMapStyle(String mapStyle) {
248-
setState(() {
249-
_nightMode = true;
250-
_controller.setMapStyle(mapStyle);
251-
});
252-
}
253-
254-
// Should only be called if _isMapCreated is true.
255248
Widget _nightModeToggler() {
256-
assert(_isMapCreated);
257249
return TextButton(
258250
child: Text('${_nightMode ? 'disable' : 'enable'} night mode'),
259-
onPressed: () {
260-
if (_nightMode) {
261-
setState(() {
262-
_nightMode = false;
263-
_controller.setMapStyle(null);
264-
});
265-
} else {
266-
_getFileData('assets/night_mode.json').then(_setMapStyle);
267-
}
251+
onPressed: () async {
252+
_nightMode = !_nightMode;
253+
final String style =
254+
_nightMode ? await _getFileData('assets/night_mode.json') : '';
255+
setState(() {
256+
_mapStyle = style;
257+
});
268258
},
269259
);
270260
}
@@ -279,6 +269,7 @@ class MapUiBodyState extends State<MapUiBody> {
279269
cameraTargetBounds: _cameraTargetBounds,
280270
minMaxZoomPreference: _minMaxZoomPreference,
281271
mapType: _mapType,
272+
style: _mapStyle,
282273
rotateGesturesEnabled: _rotateGesturesEnabled,
283274
scrollGesturesEnabled: _scrollGesturesEnabled,
284275
tiltGesturesEnabled: _tiltGesturesEnabled,
@@ -353,5 +344,11 @@ class MapUiBodyState extends State<MapUiBody> {
353344
_controller = controller;
354345
_isMapCreated = true;
355346
});
347+
// Log any style errors to the console for debugging.
348+
_controller.getStyleError().then((String? error) {
349+
if (error != null) {
350+
debugPrint(error);
351+
}
352+
});
356353
}
357354
}

packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies:
1818
# The example app is bundled with the plugin so we use a path dependency on
1919
# the parent directory to use the current plugin's version.
2020
path: ../
21-
google_maps_flutter_platform_interface: ^2.4.0
21+
google_maps_flutter_platform_interface: ^2.5.0
2222

2323
dev_dependencies:
2424
build_runner: ^2.1.10

packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart

+6
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,11 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform {
484484
return _channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
485485
}
486486

487+
@override
488+
Future<String?> getStyleError({required int mapId}) {
489+
return _channel(mapId).invokeMethod<String>('map#getStyleError');
490+
}
491+
487492
/// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the
488493
/// Google Maps widget.
489494
///
@@ -727,6 +732,7 @@ Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) {
727732
if (config.buildingsEnabled != null)
728733
'buildingsEnabled': config.buildingsEnabled!,
729734
if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!,
735+
if (config.style != null) 'style': config.style!,
730736
};
731737
}
732738

0 commit comments

Comments
 (0)