Skip to content

Commit 383bffa

Browse files
authored
[google_maps_flutter] cloud-based map styling implementation (flutter#4638)
This PR is sub-PR splitted out from the flutter/packages#3682 containing only following packages: - google_maps_flutter_web - google_maps_flutter_android - google_maps_flutter_ios Related to issue flutter#67631
1 parent 32460c7 commit 383bffa

File tree

32 files changed

+499
-19
lines changed

32 files changed

+499
-19
lines changed

packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ dependencies:
1919
# the parent directory to use the current plugin's version.
2020
path: ../
2121
google_maps_flutter_android: ^2.1.10
22-
google_maps_flutter_platform_interface: ^2.2.1
22+
google_maps_flutter_platform_interface: ^2.4.0
2323

2424
dev_dependencies:
2525
build_runner: ^2.1.10

packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
## NEXT
1+
## 2.5.0
22

3+
* Adds implementation for `cloudMapId` parameter to support cloud-based map styling.
34
* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.
45

56
## 2.4.16
7+
68
* Removes old empty override methods.
79
* Fixes unawaited_futures violations.
810

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ void setInitialCameraPosition(CameraPosition position) {
5555
options.camera(position);
5656
}
5757

58+
public void setMapId(String mapId) {
59+
options.mapId(mapId);
60+
}
61+
5862
@Override
5963
public void setCompassEnabled(boolean compassEnabled) {
6064
options.compassEnabled(compassEnabled);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar
3737
Map<String, Object> params = (Map<String, Object>) args;
3838
final GoogleMapBuilder builder = new GoogleMapBuilder();
3939

40-
Convert.interpretGoogleMapOptions(params.get("options"), builder);
40+
final Object options = params.get("options");
41+
Convert.interpretGoogleMapOptions(options, builder);
4142
if (params.containsKey("initialCameraPosition")) {
4243
CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition"));
4344
builder.setInitialCameraPosition(position);
@@ -57,6 +58,11 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar
5758
if (params.containsKey("tileOverlaysToAdd")) {
5859
builder.setInitialTileOverlays((List<Map<String, ?>>) params.get("tileOverlaysToAdd"));
5960
}
61+
final Object cloudMapId = ((Map<?, ?>) options).get("cloudMapId");
62+
if (cloudMapId != null) {
63+
builder.setMapId((String) cloudMapId);
64+
}
65+
6066
return builder.build(id, context, binaryMessenger, lifecycleProvider);
6167
}
6268
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const LatLng _kInitialMapCenter = LatLng(0, 0);
1717
const double _kInitialZoomLevel = 5;
1818
const CameraPosition _kInitialCameraPosition =
1919
CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel);
20+
const String _kCloudMapId = '000000000000000'; // Dummy map ID.
2021

2122
void googleMapsTests() {
2223
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
@@ -1178,6 +1179,32 @@ void googleMapsTests() {
11781179
expect(tileOverlayInfo1, isNull);
11791180
},
11801181
);
1182+
1183+
testWidgets(
1184+
'testCloudMapId',
1185+
(WidgetTester tester) async {
1186+
final Completer<int> mapIdCompleter = Completer<int>();
1187+
final Key key = GlobalKey();
1188+
1189+
await tester.pumpWidget(
1190+
Directionality(
1191+
textDirection: TextDirection.ltr,
1192+
child: ExampleGoogleMap(
1193+
key: key,
1194+
initialCameraPosition: _kInitialCameraPosition,
1195+
onMapCreated: (ExampleGoogleMapController controller) {
1196+
mapIdCompleter.complete(controller.mapId);
1197+
},
1198+
cloudMapId: _kCloudMapId,
1199+
),
1200+
),
1201+
);
1202+
1203+
// Await mapIdCompleter to finish to make sure map can be created with styledMapId
1204+
// Styled map
1205+
await mapIdCompleter.future;
1206+
},
1207+
);
11811208
}
11821209

11831210
class _DebugTileProvider implements TileProvider {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ class ExampleGoogleMap extends StatefulWidget {
244244
this.onCameraIdle,
245245
this.onTap,
246246
this.onLongPress,
247+
this.cloudMapId,
247248
});
248249

249250
/// Callback method for when the map is ready to be used.
@@ -346,6 +347,12 @@ class ExampleGoogleMap extends StatefulWidget {
346347
/// Which gestures should be consumed by the map.
347348
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
348349

350+
/// Identifier that's associated with a specific cloud-based map style.
351+
///
352+
/// See https://developers.google.com/maps/documentation/get-map-id
353+
/// for more details.
354+
final String? cloudMapId;
355+
349356
/// Creates a [State] for this [ExampleGoogleMap].
350357
@override
351358
State createState() => _ExampleGoogleMapState();
@@ -531,5 +538,6 @@ MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
531538
indoorViewEnabled: map.indoorViewEnabled,
532539
trafficEnabled: map.trafficEnabled,
533540
buildingsEnabled: map.buildingsEnabled,
541+
cloudMapId: map.cloudMapId,
534542
);
535543
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
6+
57
import 'package:flutter/material.dart';
68
import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
79
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
@@ -10,6 +12,7 @@ import 'animate_camera.dart';
1012
import 'lite_mode.dart';
1113
import 'map_click.dart';
1214
import 'map_coordinates.dart';
15+
import 'map_map_id.dart';
1316
import 'map_ui.dart';
1417
import 'marker_icons.dart';
1518
import 'move_camera.dart';
@@ -39,6 +42,7 @@ final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
3942
const SnapshotPage(),
4043
const LiteModePage(),
4144
const TileOverlayPage(),
45+
const MapIdPage(),
4246
];
4347

4448
/// MapsDemo is the Main Application.
@@ -74,5 +78,32 @@ void main() {
7478
final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance;
7579
// Default to Hybrid Composition for the example.
7680
(platform as GoogleMapsFlutterAndroid).useAndroidViewSurface = true;
81+
initializeMapRenderer();
7782
runApp(const MaterialApp(home: MapsDemo()));
7883
}
84+
85+
Completer<AndroidMapRenderer?>? _initializedRendererCompleter;
86+
87+
/// Initializes map renderer to the `latest` renderer type.
88+
///
89+
/// The renderer must be requested before creating GoogleMap instances,
90+
/// as the renderer can be initialized only once per application context.
91+
Future<AndroidMapRenderer?> initializeMapRenderer() async {
92+
if (_initializedRendererCompleter != null) {
93+
return _initializedRendererCompleter!.future;
94+
}
95+
96+
final Completer<AndroidMapRenderer?> completer =
97+
Completer<AndroidMapRenderer?>();
98+
_initializedRendererCompleter = completer;
99+
100+
WidgetsFlutterBinding.ensureInitialized();
101+
102+
final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance;
103+
unawaited((platform as GoogleMapsFlutterAndroid)
104+
.initializeWithRenderer(AndroidMapRenderer.latest)
105+
.then((AndroidMapRenderer initializedRenderer) =>
106+
completer.complete(initializedRenderer)));
107+
108+
return completer.future;
109+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: public_member_api_docs
6+
7+
import 'package:flutter/material.dart';
8+
import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
9+
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
10+
11+
import 'example_google_map.dart';
12+
import 'main.dart';
13+
import 'page.dart';
14+
15+
class MapIdPage extends GoogleMapExampleAppPage {
16+
const MapIdPage({Key? key})
17+
: super(const Icon(Icons.map), 'Cloud-based maps styling', key: key);
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return const MapIdBody();
22+
}
23+
}
24+
25+
class MapIdBody extends StatefulWidget {
26+
const MapIdBody({super.key});
27+
28+
@override
29+
State<StatefulWidget> createState() => MapIdBodyState();
30+
}
31+
32+
const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
33+
34+
class MapIdBodyState extends State<MapIdBody> {
35+
ExampleGoogleMapController? controller;
36+
37+
Key _key = const Key('mapId#');
38+
String? _mapId;
39+
final TextEditingController _mapIdController = TextEditingController();
40+
AndroidMapRenderer? _initializedRenderer;
41+
42+
@override
43+
void initState() {
44+
initializeMapRenderer()
45+
.then<void>((AndroidMapRenderer? initializedRenderer) => setState(() {
46+
_initializedRenderer = initializedRenderer;
47+
}));
48+
super.initState();
49+
}
50+
51+
String _getInitializedsRendererType() {
52+
switch (_initializedRenderer) {
53+
case AndroidMapRenderer.latest:
54+
return 'latest';
55+
case AndroidMapRenderer.legacy:
56+
return 'legacy';
57+
case AndroidMapRenderer.platformDefault:
58+
case null:
59+
break;
60+
}
61+
return 'unknown';
62+
}
63+
64+
void _setMapId() {
65+
setState(() {
66+
_mapId = _mapIdController.text;
67+
68+
// Change key to initialize new map instance for new mapId.
69+
_key = Key(_mapId ?? 'mapId#');
70+
});
71+
}
72+
73+
@override
74+
Widget build(BuildContext context) {
75+
final ExampleGoogleMap googleMap = ExampleGoogleMap(
76+
onMapCreated: _onMapCreated,
77+
initialCameraPosition: const CameraPosition(
78+
target: _kMapCenter,
79+
zoom: 7.0,
80+
),
81+
key: _key,
82+
cloudMapId: _mapId,
83+
);
84+
85+
final List<Widget> columnChildren = <Widget>[
86+
Padding(
87+
padding: const EdgeInsets.all(10.0),
88+
child: Center(
89+
child: SizedBox(
90+
width: 300.0,
91+
height: 200.0,
92+
child: googleMap,
93+
),
94+
),
95+
),
96+
Padding(
97+
padding: const EdgeInsets.all(10.0),
98+
child: TextField(
99+
controller: _mapIdController,
100+
decoration: const InputDecoration(
101+
hintText: 'Map Id',
102+
),
103+
)),
104+
Padding(
105+
padding: const EdgeInsets.all(10.0),
106+
child: ElevatedButton(
107+
onPressed: () => _setMapId(),
108+
child: const Text(
109+
'Press to use specified map Id',
110+
),
111+
)),
112+
if (_initializedRenderer != AndroidMapRenderer.latest)
113+
Padding(
114+
padding: const EdgeInsets.all(10.0),
115+
child: Text(
116+
'On Android, Cloud-based maps styling only works with "latest" renderer.\n\n'
117+
'Current initialized renderer is "${_getInitializedsRendererType()}".'),
118+
),
119+
];
120+
121+
return Column(
122+
crossAxisAlignment: CrossAxisAlignment.stretch,
123+
children: columnChildren,
124+
);
125+
}
126+
127+
@override
128+
void dispose() {
129+
_mapIdController.dispose();
130+
super.dispose();
131+
}
132+
133+
void _onMapCreated(ExampleGoogleMapController controllerParam) {
134+
setState(() {
135+
controller = controllerParam;
136+
});
137+
}
138+
}

packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
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.2.1
21+
google_maps_flutter_platform_interface: ^2.4.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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) {
740740
if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!,
741741
if (config.buildingsEnabled != null)
742742
'buildingsEnabled': config.buildingsEnabled!,
743+
if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!,
743744
};
744745
}
745746

0 commit comments

Comments
 (0)