Adding Google Maps to Flutter

At Flutter Live last week, we showed full-featured Google Maps in Flutter for the first time. The package is available as google_maps_flutter on pub.dartlang.org. The API is changing to be more idiomatic to Flutter, and we’ll update you when that work is done. In the meantime, if you want to play with Google Maps in Flutter using the current, controller-based API, here’s a tutorial.

This article shows you step-by-step how to add a Google Map widget to your Flutter application. Here’s what you’re going to build today:

Setup

The first step is to add the Google Maps Flutter plugin as a dependency in the pubspec.yaml file.

dependencies:
  ...
  google_maps_flutter: ^0.0.3+3

Once you do that, you need to run flutter packages get.

The next step is getting an API key. Go to the Google Maps Platform website and click “Get Started”. Check the Maps box and click Continue.

Select an existing project or create a new one, and finish the remaining steps in this dialog to get your API key.

Next, add this key to your Flutter app. For Android, you’ll specify the API key in the application manifest (android/app/src/main/AndroidManifest.xml), as follows:

manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"

For iOS, specify the API key in the application delegate

(ios/Runner/AppDelegate.m):

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:@"YOUR KEY HERE"];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

On iOS, you also need to opt-in to the embedded views preview, so add a boolean property to the app’s Info.plist file (ios/Runner/Info.plist) with the key io.flutter.embedded_views_preview and the value true:

<key>io.flutter.embedded_views_preview</key>
<true>

Adding a GoogleMap widget

Note: The plugin API described below is pre-1.0 and will change.

Now you are ready to add a GoogleMap widget! Run flutter clean to make sure the API key changes are picked up on the next build. Then, add a GoogleMap widget that covers the entire screen:

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 GoogleMapController mapController;

 final LatLng _center = const LatLng(45.521563, -122.677433);

 void _onMapCreated(GoogleMapController controller) {
   mapController = controller;
 }

 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Maps Sample App'),
         backgroundColor: Colors.green[700],
       ),
       body: GoogleMap(
         onMapCreated: _onMapCreated,
         options: GoogleMapOptions(
           cameraPosition: CameraPosition(
             target: _center,
             zoom: 11.0,
           ),
         ),
       ),
     ),
   );
 }
}

Important bits:

  • onMapCreated: required method that is called on map creation and takes a MapController as a parameter.
  • mapController: essentially the link to all map interactions and settings (…at least in this iteration of the API). This pattern is similar to other controllers available in Flutter, for example TextEditingController.
  • options: stores map settings and preferences such as cameraPosition, mapType, enabled/disabled gestures, etc. See the plugin source code for a full list of available options.
  • cameraPosition: controls which part of the world you want the map to point at.

If you run your app at this point, it should look like this:

What can you do with it?

So now you have Google Maps in your app, but you probably want to do something more interesting. What about putting Flutter widgets on top of the map, changing the map’s appearance, or adding place markers to the map? You can do it all!

Add a widget on top of the map

It’s important to remember that the GoogleMap widget is just a Flutter widget, meaning you can treat it like any other widget. This includes placing another widget on top of it. By placing the GoogleMap widget inside of a Stack widget, you can layer other Flutter widgets on top of the map widget:

body: Stack(
 children: <Widget>[
   GoogleMap(
     onMapCreated: _onMapCreated,
     options: GoogleMapOptions(
       cameraPosition: CameraPosition(
         target: _center,
         zoom: 11.0,
       ),
     ),
   ),
   Padding(
     padding: const EdgeInsets.all(16.0),
     child: Align(
       alignment: Alignment.topRight,
       child: FloatingActionButton(
         onPressed: () => print('button pressed'),
         materialTapTargetSize: MaterialTapTargetSize.padded,
         backgroundColor: Colors.green,
         child: const Icon(Icons.map, size: 36.0),
       ),
     ),
   ),
 ],
)

Change the map’s appearance

Right now, the added button doesn’t do anything interesting. Change that so that when pressed, the button toggles between two different map types: normal view and satellite view. As mentioned previously, to interact with the map, you need to use the MapController. Add a method that uses the map controller to update the MapOptions:

MapType _currentMapType = MapType.normal;

void _onMapTypeButtonPressed() {
 if (_currentMapType == MapType.normal) {
   mapController.updateMapOptions(
     GoogleMapOptions(mapType: MapType.satellite),
   );
   _currentMapType = MapType.satellite;
 } else {
   mapController.updateMapOptions(
     GoogleMapOptions(mapType: MapType.normal),
   );
   _currentMapType = MapType.normal;
 }
}

Now replace () => print(‘button pressed’) with _onMapTypeButtonPressed.

child: FloatingActionButton(
  onPressed: _onMapTypeButtonPressed,
  ...
),

Add a marker

How about creating another button that, when pressed, adds place markers to the map. Following the same pattern as before, add a button to the stack. Place another FloatingActionButton inside the Align widget from the build method:

Align(
 alignment: Alignment.topRight,
 child: Column(
   children: <Widget>[
     FloatingActionButton(
       ...
     ),
     SizedBox(height: 16.0),
     FloatingActionButton(
       onPressed: _onAddMarkerButtonPressed,
       materialTapTargetSize: MaterialTapTargetSize.padded,
       backgroundColor: Colors.green,
       child: const Icon(Icons.add_location, size: 36.0),
     ),
   ],
 )
)

Now once again, implement a method that uses the MapController to manipulate the map. In this case, you want to add place markers, so call MapController’s addMarker method.

void _onAddMarkerButtonPressed() {
 mapController.addMarker(
   MarkerOptions(
     position: LatLng(
       mapController.cameraPosition.target.latitude,
       mapController.cameraPosition.target.longitude,
     ),
     infoWindowText: InfoWindowText('Random Place', '5 Star Rating'),
     icon: BitmapDescriptor.defaultMarker,
   ),
 );
}

You also need to add trackCameraPosition: true to GoogleMapOptions in the GoogleMapWidget. This allows the mapController to track the map’s current camera position. mapController.cameraPosition.target returns the LatLng position at the center of the GoogleMap widget. This position is used in the code above to set the marker’s position on the map.

GoogleMap(
  ...
  GoogleMapOptions(
    trackCameraPosition: true,
    ...
  ),
),

You can customize the markers with different colors, for example

BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueViolet), or even with custom icons, for example BitmapDescriptor.fromAsset(‘assets/asset_name.png).

Final main.dart code

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 GoogleMapController mapController;
 MapType _currentMapType = MapType.normal;

 final LatLng _center = const LatLng(45.521563, -122.677433);

 void _onMapCreated(GoogleMapController controller) {
   mapController = controller;
 }

 void _onMapTypeButtonPressed() {
   if (_currentMapType == MapType.normal) {
     mapController.updateMapOptions(
       GoogleMapOptions(mapType: MapType.satellite),
     );
     _currentMapType = MapType.satellite;
   } else {
     mapController.updateMapOptions(
       GoogleMapOptions(mapType: MapType.normal),
     );
     _currentMapType = MapType.normal;
   }
 }

 void _onAddMarkerButtonPressed() {
   mapController.addMarker(
     MarkerOptions(
       position: LatLng(
         mapController.cameraPosition.target.latitude,
         mapController.cameraPosition.target.longitude,
       ),
       infoWindowText: InfoWindowText('Random Place', '5 Star Rating'),
       icon: BitmapDescriptor.defaultMarker,
     ),
   );
 }

 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       appBar: AppBar(
         title: Text('Maps Sample App'),
         backgroundColor: Colors.green[700],
       ),
       body: Stack(
         children: <Widget>[
           GoogleMap(
             onMapCreated: _onMapCreated,
             options: GoogleMapOptions(
               trackCameraPosition: true,
               cameraPosition: CameraPosition(
                 target: _center,
                 zoom: 11.0,
               ),
             ),
           ),
           Padding(
             padding: const EdgeInsets.all(16.0),
             child: Align(
               alignment: Alignment.topRight,
               child: Column(
                 children: <Widget>[
                   FloatingActionButton(
                     onPressed: _onMapTypeButtonPressed,
                     materialTapTargetSize: MaterialTapTargetSize.padded,
                     backgroundColor: Colors.green,
                     child: const Icon(Icons.map, size: 36.0),
                   ),
                   const SizedBox(height: 16.0),
                   FloatingActionButton(
                     onPressed: _onAddMarkerButtonPressed,
                     materialTapTargetSize: MaterialTapTargetSize.padded,
                     backgroundColor: Colors.green,
                     child: const Icon(Icons.add_location, size: 36.0),
                   ),
                 ],
               )
             ),
           ),
         ],
       ),
     ),
   );
 }
}

What else can you do?

Once again, the GoogleMap widget is just a widgetThis means you can place widgets on top of it (like you just did), you can place it inside other widgets (like a ListView, for example), or if you’re feeling a bit wild, you could even place it in a Transform widget*.

The possibilities extend as far as you’d like to take them. Checkout the Place Tracker app in flutter/samples for a more complete Google Maps demo. You can also checkout the google_maps_flutter plugin example app for a demonstration on using the plugin.

*Using a transform widget to rotate the map is currently not supported on iOS. It might be in the future, but it is not the biggest priority for the plugin at this time.

Please remember that this controller-based API is not the final one. The google_maps_flutter plugin is under heavy development right now to make it more Flutter-like. We will update this article once that work is done.

Leave a Reply

Your email address will not be published.