Dart URI + Google Static Maps API

At this point, it doesn't seem likely that Google Maps is going to beintegrated into Flutter any time soon.

Luckily, for the time being, it's easy to fake Google Maps in your app.

Demo Screenshot

Google Static Maps API

Google Static Maps simply serves up images. While not ideal, because it's not interactive, it can give you a temporary solution.

The url that you create renders the image on the fly. You don't have to make a request with a body and a method and headers. The url that you create to make the request is the url you render.

In other words, on the web with HTML you would use Static Maps like this:

  1. <img scr='https://maps.googleapis.com/maps/api/staticmap?center=37.0902%2C-95.7192&zoom=4&size=600x400&API_KEY' />

This Url is making the API call to Google, and rendering.

Not only is easy, it's fast. So you can, if you need, make it interactive by just rendering images in reaction to stateChange (such as device location);

This is pretty awesome. You don't have to fiddle with anything asynchronous.You simply construct your URL and use it as if it's an image.

1. Get Set Up

  1. $ flutter create static-maps-app
  • Get a Google Maps Api Key

    • Click 'Get Started'
    • Click 'Google Static Maps API'
    • Click 'Get a Key'
    • Name the Project (such as 'static-maps')
    • Click 'Confirm and Get Key'

2. Start Building

In your project directory you'll find a whole sample app. We're only concernedwith the lib directory for now. For sample or simple apps, you may never needto touch any other files. I've been working with Flutter on a large app atwork for a few months and I think I've only had to open the iOS and Androidfiles a handful of times.

  1. $ cd static-maps-app
  2. $ flutter run

To begin with, replace the code in main.dart. You can leave MyApp class and MyAppHomePage class alone. We're going to be modifying _MyHomePageState.

(Additional Info: Stateful vs Stateless Widgets)

  1. class _MyHomePageState extends State<MyHomePage> {
  2. // be sure to replace 'YOUR API KEY' with your actual API key.
  3. String googleMapsApiKey = 'YOUR API KEY';
  4. @override
  5. Widget build(BuildContext context) {
  6. return new Scaffold(
  7. appBar: new AppBar(
  8. title: new Text(widget.title),
  9. ),
  10. body: new Image.network('https://maps.googleapis.com/maps/api/staticmap?center=37.0902%2C-95.7192&zoom=4&size=600x400&googleMapsApiKey'),
  11. );
  12. }
  13. }

Look at that. In a matter of minutes you got a map rendering.

Demo App Screen Shot

But this isn't entirely useful. We probably want to render a map based on a users location, and possibly provide a marker based on some input or data.

3. Building Your Static Maps API URL

In order to make any sort of real use of Static Maps, you're going to want tobuild URLs dynamically, not use hardcoded URLs. First lets take a look at whatinformation we need for the static map. Once wecan build URLs dynamically, we can add map interactivity. (Or, we can at least fake it.)

In order to implement this building logic, first take a look at what the API isexpecting.

  • A base url + the static maps api path:https://maps.googleapis.com/maps/api/staticmap?
  • Required query params:

    • size: size in pixels passed in as a string size={horizontal_value_pixels}x{vertical_value_pixels}
    • zoom: an integer (between 0 (to view Earth) and 20 (down to Building views)). zoom=6
    • center: either a pair of coordinates {latitude,longitude} or a string address center="moscone center, san francisco, CA".
    • Note: Zoom and center are only required if there are no markers. For now, we don't have markers.
    • Note 2: Query params are seperated by &.

This is a totally valid map URL:

  1. https://maps.googleapis.com/maps/api/staticmap?size=600X400&zoom=6&center=-25.0324,45.9324&*YOUR_API_KEY*
  • Optional Query Parameters

    • maptype: The visual style of the map. roadmap, satelite, hybrid, terrain.
    • markers: where to display markers on the map. Covered below.
    • path: provides connected points on the map. Covered below.

Note: there are more optional params, but these are the ones that I finduseful.See All Params

4. Dart Uri Library

Let's start another file so we can separate this logic out. In the lib folder, create static_maps_provider.dart.

In that file, create the Widget basics:

  1. import 'package:flutter/material.dart';
  2. class StaticMapsProvider extends StatefulWidget {
  3. @override
  4. _StaticMapsProviderState createState() => new _StaticMapsProviderState();
  5. }
  6. class _StaticMapsProviderState extends State<StaticMapsProvider> {
  7. @override
  8. Widget build(BuildContext context) {
  9. return new Image.network('https://maps.googleapis.com/maps/api/staticmap?center=37.0902%2C-95.7192&zoom=4&size=600x400&googleMapsApiKey');
  10. }
  11. }

Now, refactor main.dart's _MyHomePageState class:

  1. // in the build method:
  2. Widget build(BuildContext context) {
  3. return new Scaffold(
  4. appBar: new AppBar(
  5. title: new Text(widget.title),
  6. ),
  7. body: new StaticMapsProvider(googleMapsApiKey),
  8. );
  9. }

Back in the new static_maps_provider file, we'll start building our Uri. The Uri class is in dart:core so there's no need to import.

Add the following code. The Uri class constructor is looking for parameters so it can build a URL. In this initial part of the _buildUrl function, we're going to build the base. These properties will be part of every Url you need. You can read about the parameters that the Uri constructor takes here.

Also, be sure to pass your Api Key into the Static Maps Provider.

  1. class StaticMapsProvider extends StatefulWidget {
  2. final String googleMapsApiKey;
  3. StaticMapsProvider(this.googleMapsApiKey);
  4. @override
  5. _StaticMapsProviderState createState() => new _StaticMapsProviderState();
  6. }
  7. class _StaticMapsProviderState extends State<StaticMapsProvider> {
  8. Uri renderURL;
  9. _buildUrl() {
  10. var baseUri = new Uri(
  11. scheme: 'https',
  12. host: 'maps.googleapis.com',
  13. port: 443,
  14. path: '/maps/api/staticmap',
  15. queryParameters: {});
  16. setState(() {
  17. renderURL = baseUri;
  18. });
  19. }
  20. @override
  21. _buildUrl();
  22. Widget build(BuildContext context) {
  23. return new Image.network(renderUrl.toString());
  24. }
  25. }

In this class, we're basically establishing our renderUrl and passing in to the Image widget's constructor.

Hot reload your app. The map is gone, an error is thrown. 😢 This is becausewe're missing some required query params for the API. We'll handle this withsome default values. The default values could be hardcoded in, but later we'llwant them to be constants that we can edit them based on user input.

Your _StaticMapsProviderState Class:

  1. class _StaticMapsProviderState extends State<StaticMapsProvider> {
  2. Uri renderUrl;
  3. static const int defaultWidth = 600;
  4. static const int defaultHeight = 400;
  5. Map<String, String> defaultLocation = {
  6. "latitude": '37.0902',
  7. "longitude": '-95.7192'
  8. };
  9. _buildUrl() {
  10. var baseUri = new Uri(
  11. scheme: 'https',
  12. host: 'maps.googleapis.com',
  13. port: 443,
  14. path: '/maps/api/staticmap',
  15. queryParameters: {
  16. 'size': '${defaultWidth}x$defaultHeight',
  17. 'center': '${defaultLocation['latitude']},${defaultLocation['longitude']}',
  18. 'zoom': '4',
  19. '${widget.googleMapsApiKey}' : ''
  20. });
  21. //...

At this point, you're rendering a map that shows the United States. You'vestarted building our Uri's dynamically. Not bad for a few minutes worth ofwork! And we've only leveraged what's built into Flutter and Dart. No packages, no plugins… yet.

The next step is to render maps that are useful. We'll need the get the userslocation, and accept user input, and build dynamically generated Uri's. We'llalso have a chance to dabble in Flutters UI system.