Skip to content

Commit

Permalink
[google_maps_flutter] cloud-based map styling implementation (#4638)
Browse files Browse the repository at this point in the history
This PR is sub-PR splitted out from the #3682
containing only following packages:
 - google_maps_flutter_web
 - google_maps_flutter_android
 - google_maps_flutter_ios

Related to issue flutter/flutter#67631
  • Loading branch information
jokerttu authored Aug 24, 2023
1 parent 32460c7 commit 383bffa
Show file tree
Hide file tree
Showing 32 changed files with 499 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
# the parent directory to use the current plugin's version.
path: ../
google_maps_flutter_android: ^2.1.10
google_maps_flutter_platform_interface: ^2.2.1
google_maps_flutter_platform_interface: ^2.4.0

dev_dependencies:
build_runner: ^2.1.10
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## NEXT
## 2.5.0

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

## 2.4.16

* Removes old empty override methods.
* Fixes unawaited_futures violations.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ void setInitialCameraPosition(CameraPosition position) {
options.camera(position);
}

public void setMapId(String mapId) {
options.mapId(mapId);
}

@Override
public void setCompassEnabled(boolean compassEnabled) {
options.compassEnabled(compassEnabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar
Map<String, Object> params = (Map<String, Object>) args;
final GoogleMapBuilder builder = new GoogleMapBuilder();

Convert.interpretGoogleMapOptions(params.get("options"), builder);
final Object options = params.get("options");
Convert.interpretGoogleMapOptions(options, builder);
if (params.containsKey("initialCameraPosition")) {
CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition"));
builder.setInitialCameraPosition(position);
Expand All @@ -57,6 +58,11 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar
if (params.containsKey("tileOverlaysToAdd")) {
builder.setInitialTileOverlays((List<Map<String, ?>>) params.get("tileOverlaysToAdd"));
}
final Object cloudMapId = ((Map<?, ?>) options).get("cloudMapId");
if (cloudMapId != null) {
builder.setMapId((String) cloudMapId);
}

return builder.build(id, context, binaryMessenger, lifecycleProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const LatLng _kInitialMapCenter = LatLng(0, 0);
const double _kInitialZoomLevel = 5;
const CameraPosition _kInitialCameraPosition =
CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel);
const String _kCloudMapId = '000000000000000'; // Dummy map ID.

void googleMapsTests() {
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
Expand Down Expand Up @@ -1178,6 +1179,32 @@ void googleMapsTests() {
expect(tileOverlayInfo1, isNull);
},
);

testWidgets(
'testCloudMapId',
(WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Key key = GlobalKey();

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: ExampleGoogleMap(
key: key,
initialCameraPosition: _kInitialCameraPosition,
onMapCreated: (ExampleGoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
cloudMapId: _kCloudMapId,
),
),
);

// Await mapIdCompleter to finish to make sure map can be created with styledMapId
// Styled map
await mapIdCompleter.future;
},
);
}

class _DebugTileProvider implements TileProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ class ExampleGoogleMap extends StatefulWidget {
this.onCameraIdle,
this.onTap,
this.onLongPress,
this.cloudMapId,
});

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

/// 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 [ExampleGoogleMap].
@override
State createState() => _ExampleGoogleMapState();
Expand Down Expand Up @@ -531,5 +538,6 @@ MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
indoorViewEnabled: map.indoorViewEnabled,
trafficEnabled: map.trafficEnabled,
buildingsEnabled: map.buildingsEnabled,
cloudMapId: map.cloudMapId,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -39,6 +42,7 @@ final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
const SnapshotPage(),
const LiteModePage(),
const TileOverlayPage(),
const MapIdPage(),
];

/// MapsDemo is the Main Application.
Expand Down Expand Up @@ -74,5 +78,32 @@ void main() {
final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance;
// Default to Hybrid Composition for the example.
(platform as GoogleMapsFlutterAndroid).useAndroidViewSurface = true;
initializeMapRenderer();
runApp(const MaterialApp(home: MapsDemo()));
}

Completer<AndroidMapRenderer?>? _initializedRendererCompleter;

/// Initializes map renderer to the `latest` renderer type.
///
/// The renderer must be requested before creating GoogleMap instances,
/// as the renderer can be initialized only once per application context.
Future<AndroidMapRenderer?> initializeMapRenderer() async {
if (_initializedRendererCompleter != null) {
return _initializedRendererCompleter!.future;
}

final Completer<AndroidMapRenderer?> completer =
Completer<AndroidMapRenderer?>();
_initializedRendererCompleter = completer;

WidgetsFlutterBinding.ensureInitialized();

final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance;
unawaited((platform as GoogleMapsFlutterAndroid)
.initializeWithRenderer(AndroidMapRenderer.latest)
.then((AndroidMapRenderer initializedRenderer) =>
completer.complete(initializedRenderer)));

return completer.future;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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 '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';

import 'example_google_map.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<StatefulWidget> createState() => MapIdBodyState();
}

const LatLng _kMapCenter = LatLng(52.4478, -3.5402);

class MapIdBodyState extends State<MapIdBody> {
ExampleGoogleMapController? controller;

Key _key = const Key('mapId#');
String? _mapId;
final TextEditingController _mapIdController = TextEditingController();
AndroidMapRenderer? _initializedRenderer;

@override
void initState() {
initializeMapRenderer()
.then<void>((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 ExampleGoogleMap googleMap = ExampleGoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: const CameraPosition(
target: _kMapCenter,
zoom: 7.0,
),
key: _key,
cloudMapId: _mapId,
);

final List<Widget> columnChildren = <Widget>[
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 (_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(ExampleGoogleMapController controllerParam) {
setState(() {
controller = controllerParam;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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_platform_interface: ^2.2.1
google_maps_flutter_platform_interface: ^2.4.0

dev_dependencies:
build_runner: ^2.1.10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) {
if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!,
if (config.buildingsEnabled != null)
'buildingsEnabled': config.buildingsEnabled!,
if (config.cloudMapId != null) 'cloudMapId': config.cloudMapId!,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_maps_flutter_android
description: Android implementation of the google_maps_flutter plugin.
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
version: 2.4.16
version: 2.5.0

environment:
sdk: ">=2.19.0 <4.0.0"
Expand All @@ -21,7 +21,7 @@ dependencies:
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^2.0.1
google_maps_flutter_platform_interface: ^2.2.1
google_maps_flutter_platform_interface: ^2.4.0
stream_transform: ^2.0.0

dev_dependencies:
Expand Down
Loading

0 comments on commit 383bffa

Please sign in to comment.