Eric Windmill
Flutter’s core team is working on supporting a feature that is used endlessly in modern applications: Google Maps. But it isn’t here yet.
For the time being, it’s easy to fake Google Maps in your app.
The purpose of this demo is to show how you can use Maps in your flutter app today, before Maps are supported. In addition to simply rendering a map, we’ll take a look at accepting user input, changing state, and Flutter basics.
It’s going to be detailed enough that you should be able to follow along if you’re new to Flutter.
I’ll walk you through building this map demo app that I made.
This post is inspired by this post about using Flutter Animations.
Google Static Maps simply serves up images. While not ideal, because it’s not interactive, it can give you a temporary solution and its easy.
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 you would use Static Maps like this:
<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 asynchronous programming. There’s no trying and catching of errors. You simply construct your URL and use it as if it’s an image.
Note: If this is your first Flutter Project, you need to install and set up flutter first.
$ flutter create static-maps-app
In your project directory you’ll find a whole sample app. We’re only concerned with the lib
directory for now. For sample or simple apps, you may never need to touch any other files. I’ve been working with Flutter on a large app at work for a few months and I think I’ve only had to open the iOS and Android files once.
$ cd static-maps-app
$ 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)
class _MyHomePageState extends State<MyHomePage> {
// be sure to replace 'YOUR API KEY' with your actual API key.
String googleMapsApiKey = 'YOUR API KEY';
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Image.network('https://maps.googleapis.com/maps/api/staticmap?center=37.0902%2C-95.7192&zoom=4&size=600x400&googleMapsApiKey'),
);
}
}
Look at that. In a matter of minutes we went from new project to rendering a map.
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.
First, I don’t know the difference between a URI and a URL, and I may accidentally be using the terms interchangeably.
In order to make any sort of real use of Static Maps, you’re going to want to build URLs dynamically, not use hardcoded URLs. First lets take a look at what information we need for the static map, and the format expectation. Once we can build URLs dynamically, we can add map interactivity. (Or, we can at least fake it.)
In order to implelemt this building logic, first lets figure out what API is expecting.
A base url + the static maps api path:
https://maps.googleapis.com/maps/api/staticmap?
Required query params:
size={horizontal_value_pixels}x{vertical_value_pixels}
0
(to view Earth) and 20
(down to Building views)). zoom=6
{latitude,longitude}
or a string address center="moscone center, san francisco, CA"
. &
.This is a totally valid map URL:
https://maps.googleapis.com/maps/api/staticmap?size=600X400&zoom=6¢er=-25.0324,45.9324&*YOUR_API_KEY*
Optional Query Parameters
roadmap
, satelite
, hybrid
, terrain
.Note: there are more optional params, but these are the ones that I find useful. See All Params
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:
import 'package:flutter/material.dart';
class StaticMapsProvider extends StatefulWidget {
_StaticMapsProviderState createState() => new _StaticMapsProviderState();
}
class _StaticMapsProviderState extends State<StaticMapsProvider> {
Widget build(BuildContext context) {
return new Image.network('https://maps.googleapis.com/maps/api/staticmap?center=37.0902%2C-95.7192&zoom=4&size=600x400&googleMapsApiKey'),;
}
}
Now, refactor main.dart
’s _MyHomePageState
class:
// in the build method:
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new StaticMapsProvider(googleMapsApiKey),
);
}
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.
class StaticMapsProvider extends StatefulWidget {
final String googleMapsApiKey;
StaticMapsProvider(this.googleMapsApiKey);
_StaticMapsProviderState createState() => new _StaticMapsProviderState();
}
class _StaticMapsProviderState extends State<StaticMapsProvider> {
Uri renderURL;
_buildUrl() {
var baseUri = new Uri(
scheme: 'https',
host: 'maps.googleapis.com',
port: 443,
path: '/maps/api/staticmap',
queryParameters: {});
setState(() {
renderURL = baseUri;
});
}
_buildUrl();
Widget build(BuildContext context) {
return new Image.network(renderUrl.toString());
}
}
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 because we’re missing some required query params for the API. We’ll handle this with some default values. The default values could be hardcoded in, but later we’ll want them to be constants that we can edit them based on user input.
Your _StaticMapsProviderState
Class:
class _StaticMapsProviderState extends State<StaticMapsProvider> {
Uri renderUrl;
static const int defaultWidth = 600;
static const int defaultHeight = 400;
Map<String, String> defaultLocation = {
"latitude": '37.0902',
"longitude": '-95.7192'
};
_buildUrl() {
var baseUri = new Uri(
scheme: 'https',
host: 'maps.googleapis.com',
port: 443,
path: '/maps/api/staticmap',
queryParameters: {
'size': '${defaultWidth}x$defaultHeight',
'center': '${defaultLocation['latitude']},${defaultLocation['longitude']}',
'zoom': '4',
'${widget.googleMapsApiKey}' : ''
});
//...
At this point, you’re rendering a map that shows the United States. You’ve started building our Uri’s dynamically. Not bad for a few minutes worth of work! 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 users location, and accept user input, and build dynamically generated Uri’s. We’ll also have a chance to dabble in Flutters UI system.
Sign up for my mailing list to receive new articles, mainly about Dart and Flutter, and other programming technologies.