Getting To Know Flutter: Google Maps Integration

In this tutorial you will learn how to add an interactive map provided by Google Maps in your Flutter app. We will use the official Google Maps package https://pub.dev/packages/google_maps_flutter.

We will skip the "Getting started" part that you can find in the home page of the package to go directly o the juicy part.
In brief you have to create an API Key from the Google Cloud Platform and set the key n your native Android and iOS projects.

We will start from a simple app, which is the one that you can find in the Readme of the package, a simple map centered in the Google Plex:

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Google Maps Demo',
      home: GoogleMapsFlutter(),
    );
  }
}

class GoogleMapsFlutter extends StatefulWidget {
  
  _GoogleMapsFlutterState createState() => _GoogleMapsFlutterState();
}

class _GoogleMapsFlutterState extends State<GoogleMapsFlutter> {
  Completer<GoogleMapController> _controller = Completer();

  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        initialCameraPosition: _kGooglePlex,
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      ),
    );
  }
}

Add a marker

Let's add a marker in the center of the google plex, to do this the GoogleMap Widget has a markersparameter that takes a Set of Markers objects. So the body of our Scaffold becomes:

GoogleMap(
        mapType: MapType.normal,
        initialCameraPosition: _kGooglePlex,
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
        markers: Set.from(
          [
            Marker(
              icon: BitmapDescriptor.defaultMarker,
              markerId: MarkerId('google_plex'),
              position: LatLng(
                _kGooglePlex.target.latitude,
                _kGooglePlex.target.longitude,
              ),
            ),
          ],
        ),
      )

When creating the Marker you can specify the content of the InfoWindow that will appear if the marker is tapped. With the onTap parameter you can also set a function that will be called if the marker is tapped.

Marker(
              icon: BitmapDescriptor.defaultMarker,
              markerId: MarkerId('google_plex'),
              position: LatLng(
                _kGooglePlex.target.latitude,
                _kGooglePlex.target.longitude,
              ),
              infoWindow: InfoWindow(
                title: "You've tapped the Google Plex",
                snippet: 'Enjoy',
              ),
              onTap: () {
                print('Marker tapped');
              }
            )

Use a custom image for the marker

To use a custom image for your markers you will have to create a BitmapDescriptor, don't worry you can create one of it from an image from the assets but this is an asynchronous function so we will have to load our image, and our markers, in the initState function. Le's define 2 variables in our state, one for the bitmap descriptor and one for our markers and populate them in initState.

BitmapDescriptor? _markerBitmap;
  Set<Marker>? _markers;

  
  void initState() {
    _loadMarkers();
    super.initState();
  }

  void _loadMarkers() async {
    _markerBitmap = await BitmapDescriptor.fromAssetImage(
      ImageConfiguration.empty,
      'assets/location_marker.png',
    );

    _markers = Set.from([
      Marker(
        icon: _markerBitmap ?? BitmapDescriptor.defaultMarker,
        markerId: MarkerId('google_plex'),
        position: LatLng(
          _kGooglePlex.target.latitude,
          _kGooglePlex.target.longitude,
        ),
        infoWindow: InfoWindow(
          title: "You've tapped the Google Plex",
          snippet: 'Enjoy',
        ),
        onTap: () {
          print('Marker tapped');
        },
      ),
    ]);

    setState(() {});
  }

Now we need to use the markers that we've just created in our GoogleMap Widget.

GoogleMap(
        mapType: MapType.normal,
        initialCameraPosition: _kGooglePlex,
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
        markers: _markers ?? Set(),
      )

Ensure that all markers are visible

If you have a lot of markers and you don't know their positions, for example if the markers come from an API call, you will like to have all of them visible in the map. To do this we need to do the following:

  • Calculate the maps bounds to fit all the marker
  • Create a Camera request update
  • Perform the camera request update on the GoogleMapController object to move the camera

So our function to load the markers become:

void _loadMarkers() async {
    _markerBitmap = await BitmapDescriptor.fromAssetImage(
      ImageConfiguration.empty,
      'assets/location_marker.png',
    );

    // The positions of our markers
    List<LatLng> positions = [
      LatLng(44.968046, -94.420307),
      LatLng(44.33328, -89.132008),
      LatLng(33.755787, -116.359998),
      LatLng(33.844843, -116.54911),
      LatLng(44.92057, -93.44786),
      LatLng(44.240309, -91.493619),
      LatLng(44.968041, -94.419696),
      LatLng(44.333304, -89.132027),
      LatLng(33.755783, -116.360066),
      LatLng(33.844847, -116.549069),
    ];

    // Create the markers from the positions
    _markers = Set.from(
      positions
          .map(
            (e) => Marker(
              icon: _markerBitmap ?? BitmapDescriptor.defaultMarker,
              markerId: MarkerId('${e.latitude}-${e.longitude}'),
              position: LatLng(e.latitude, e.longitude),
              infoWindow: InfoWindow(
                title: "You've tapped ${e.latitude}-${e.longitude}",
                snippet: 'Enjoy',
              ),
              onTap: () {
                print('Marker tapped');
              },
            ),
          )
          .toList(),
    );

    // Calculate the bounds to fit all the markers
    var bounds = _boundsFromLatLngList(positions);

    // Create the camera update with the bounds calculated
    CameraUpdate u2 = CameraUpdate.newLatLngBounds(bounds, 50);

    // Animate the camera to update
    GoogleMapController googleMapController = await _controller.future;
    googleMapController.animateCamera(u2);

    setState(() {});
  }

  LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
    assert(list.isNotEmpty);
    double? x0, x1, y0, y1;
    for (LatLng latLng in list) {
      if (x0 == null) {
        x0 = x1 = latLng.latitude;
        y0 = y1 = latLng.longitude;
      } else {
        if (latLng.latitude > (x1 ?? 0)) x1 = latLng.latitude;
        if (latLng.latitude < x0) x0 = latLng.latitude;
        if (latLng.longitude > (y1 ?? 0)) y1 = latLng.longitude;
        if (latLng.longitude < (y0 ?? double.infinity)) y0 = latLng.longitude;
      }
    }
    return LatLngBounds(
      northeast: LatLng(x1 ?? 0, y1 ?? 0),
      southwest: LatLng(x0 ?? 0, y0 ?? 0),
    );
  }

Moving forward

If you want to find a complete example check out this repository.
You can use this as a base to implement an awesome map in your application.

19