Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
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
4 changes: 4 additions & 0 deletions packages/google_maps_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.16

* Add support for custom map styling.

## 0.5.15+1

* Add missing template type parameter to `invokeMethod` calls.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.Polyline;
Expand Down Expand Up @@ -317,6 +318,24 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
result.success(googleMap.getUiSettings().isMyLocationButtonEnabled());
break;
}
case "map#setStyle":
{
String mapStyle = (String) call.arguments;
boolean mapStyleSet;
if (mapStyle == null) {
mapStyleSet = googleMap.setMapStyle(null);
} else {
mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle));
}
ArrayList<Object> mapStyleResult = new ArrayList<>(2);
mapStyleResult.add(mapStyleSet);
if (!mapStyleSet) {
mapStyleResult.add(
"Unable to set the map style. Please check console logs for errors.");
}
result.success(mapStyleResult);
break;
}
default:
result.notImplemented();
}
Expand Down
162 changes: 162 additions & 0 deletions packages/google_maps_flutter/example/assets/night_mode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
[
{
"elementType": "geometry",
"stylers": [
{
"color": "#242f3e"
}
]
},
{
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#746855"
}
]
},
{
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#242f3e"
}
]
},
{
"featureType": "administrative.locality",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#d59563"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#d59563"
}
]
},
{
"featureType": "poi.park",
"elementType": "geometry",
"stylers": [
{
"color": "#263c3f"
}
]
},
{
"featureType": "poi.park",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#6b9a76"
}
]
},
{
"featureType": "road",
"elementType": "geometry",
"stylers": [
{
"color": "#38414e"
}
]
},
{
"featureType": "road",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#212a37"
}
]
},
{
"featureType": "road",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9ca5b3"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry",
"stylers": [
{
"color": "#746855"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#1f2835"
}
]
},
{
"featureType": "road.highway",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#f3d19c"
}
]
},
{
"featureType": "transit",
"elementType": "geometry",
"stylers": [
{
"color": "#2f3948"
}
]
},
{
"featureType": "transit.station",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#d59563"
}
]
},
{
"featureType": "water",
"elementType": "geometry",
"stylers": [
{
"color": "#17263c"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#515c6d"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#17263c"
}
]
}
]

35 changes: 35 additions & 0 deletions packages/google_maps_flutter/example/lib/map_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter/services.dart' show rootBundle;

import 'page.dart';

Expand Down Expand Up @@ -49,6 +50,8 @@ class MapUiBodyState extends State<MapUiBody> {
bool _zoomGesturesEnabled = true;
bool _myLocationEnabled = true;
bool _myLocationButtonEnabled = true;
GoogleMapController _controller;
bool _nightMode = false;

@override
void initState() {
Expand Down Expand Up @@ -183,6 +186,36 @@ class MapUiBodyState extends State<MapUiBody> {
);
}

Future<String> _getFileData(String path) async {
return await rootBundle.loadString(path);
}

void _setMapStyle(String mapStyle) {
setState(() {
_nightMode = true;
_controller.setMapStyle(mapStyle);
});
}

Widget _nightModeToggler() {
if (!_isMapCreated) {
return null;
}
return FlatButton(
child: Text('${_nightMode ? 'disable' : 'enable'} night mode'),
onPressed: () {
if (_nightMode) {
setState(() {
_nightMode = false;
_controller.setMapStyle(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can _controller be null here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this is only called from _nightModeToggler where we first check for _isMapCreated which is set alongside with _controller.

});
} else {
_getFileData('assets/night_mode.json').then(_setMapStyle);
}
},
);
}

@override
Widget build(BuildContext context) {
final GoogleMap googleMap = GoogleMap(
Expand Down Expand Up @@ -236,6 +269,7 @@ class MapUiBodyState extends State<MapUiBody> {
_zoomToggler(),
_myLocationToggler(),
_myLocationButtonToggler(),
_nightModeToggler(),
],
),
),
Expand All @@ -256,6 +290,7 @@ class MapUiBodyState extends State<MapUiBody> {

void onMapCreated(GoogleMapController controller) {
setState(() {
_controller = controller;
_isMapCreated = true;
});
}
Expand Down
69 changes: 69 additions & 0 deletions packages/google_maps_flutter/example/test_driver/google_maps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -429,4 +429,73 @@ void main() {
await inspector.isMyLocationButtonEnabled();
expect(myLocationButtonEnabled, true);
});

test('testSetMapStyle valid Json String', () async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();

await pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: _kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
));

final GoogleMapController controller = await controllerCompleter.future;
final String mapStyle =
'[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]';
await controller.setMapStyle(mapStyle);
});

test('testSetMapStyle invalid Json String', () async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();

await pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: _kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
));

final GoogleMapController controller = await controllerCompleter.future;

try {
await controller.setMapStyle('invalid_value');
fail('expected MapStyleException');
} on MapStyleException catch (e) {
expect(e.cause,
'The data couldn’t be read because it isn’t in the correct format.');
}
});

test('testSetMapStyle null string', () async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();

await pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: _kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
));

final GoogleMapController controller = await controllerCompleter.future;
await controller.setMapStyle(null);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- (void)setZoomGesturesEnabled:(BOOL)enabled;
- (void)setMyLocationEnabled:(BOOL)enabled;
- (void)setMyLocationButtonEnabled:(BOOL)enabled;
- (NSString *)setMapStyle:(NSString *)mapStyle;
@end

// Defines map overlay controllable from Flutter.
Expand Down
23 changes: 23 additions & 0 deletions packages/google_maps_flutter/ios/Classes/GoogleMapController.m
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
} else if ([call.method isEqualToString:@"map#isMyLocationButtonEnabled"]) {
NSNumber* isMyLocationButtonEnabled = @(_mapView.settings.myLocationButton);
result(isMyLocationButtonEnabled);
} else if ([call.method isEqualToString:@"map#setStyle"]) {
NSString* mapStyle = [call arguments];
NSString* error = [self setMapStyle:mapStyle];
if (error == nil) {
result(@[ @(YES) ]);
} else {
result(@[ @(NO), error ]);
}
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -308,6 +316,21 @@ - (void)setMyLocationButtonEnabled:(BOOL)enabled {
_mapView.settings.myLocationButton = enabled;
}

- (NSString*)setMapStyle:(NSString*)mapStyle {
if (mapStyle == (id)[NSNull null] || mapStyle.length == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we don't seem to cast to (id) elsewhere, do we need 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.

I get an IDE warning about comparison between incompatible pointer types, we could either cast it to (NSString*) or (id) to address that and I chose the latter. It shouldn't be a big deal if we choose to remove the cast but seems reasonable to leave it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Totally reasonable was just wondering why this is different from the other similar checks in this file. Taking another look I guess it's because the other instances here are access NSArray elements.

_mapView.mapStyle = nil;
return nil;
}
NSError* error;
GMSMapStyle* style = [GMSMapStyle styleWithJSONString:mapStyle error:&error];
Copy link
Contributor

Choose a reason for hiding this comment

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

is there any useful information in error we should send back to Dart?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The error has some more details about what the issue with the JSON is. Though it is useful to know what the error is for fixing the style, I don't think we can take any action based on the specifics of the error on the dart side in this case.

Typical action on error would be to keep the existing style (which is the default) or change to an empty/alternate style, which the user can currently do by checking the return value.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should pass back whatever information we have to the Dart side. e.g it's possible that I'm building an app for editing and previewing map styles, in which case I would even want to show the error to the user.

if (!style) {
return [error localizedDescription];
} else {
_mapView.mapStyle = style;
return nil;
}
}

#pragma mark - GMSMapViewDelegate methods

- (void)mapView:(GMSMapView*)mapView willMove:(BOOL)gesture {
Expand Down
Loading