Fourth post in the series of Flutter and Google Maps.
In the Third post we have implemented conversion of coordinates to human-readable address using Google Maps API.
In this post, I will show you how to show a static map in Flutter Google Maps.
Table of contents
Open Table of contents
Desired Outcome
We want to show user a static map image on the Home screen.
Prerequisites
Google Maps Static map
is a simple image that shows a map with markers, lines, and shapes.
It requires an API key to access the Google Maps API.
Before implementing your solution, spend time reading the Google Maps Static API documentation .
To summarize approach: you will send a network request to https://maps.googleapis.com/maps/api/staticmap
with various parameters
to get the desired map image.
The parameter list is big and provides you with a lot of flexibility to customize the map image.
In this post, we will show a simple map with a marker at a specific location with a zoom level, also we will set maptype
as roadmap
.
Once we are done with this example, I encourage you to play with other parameters and see how it affects the map image.
Home Screen preparation
Let’s start by preparing the Home screen to show the static map.
Follow code snippet below to update the home.dart
file.
import 'package:flutter/foundation.dart' ;
import 'package:flutter/material.dart' ;
import 'package:flutter_dotenv/flutter_dotenv.dart' ;
import 'package:google_maps_project/models/user_location.dart' ;
import 'package:google_maps_project/screens/map.dart' ;
import 'package:http/http.dart' as http;
import 'package:location/location.dart' ;
class HomeScreen extends StatefulWidget {
const HomeScreen ({ super .key});
State < HomeScreen > createState () {
return _HomeScreenState ();
class _HomeScreenState extends State < HomeScreen > {
final String _apiKey = dotenv. get ( 'GOOGLE_MAPS_API_KEY' );
UserLocation _userLocation =
const UserLocation (latitude : 37.422 , longitude : - 122.084 );
var _isLoadingAddress = false ;
var _parsedAddress = 'Unknown Address' ;
void _getCurrentLocation () async {
Location location = Location ();
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location. serviceEnabled ();
serviceEnabled = await location. requestService ();
permissionGranted = await location. hasPermission ();
if (permissionGranted == PermissionStatus .denied) {
permissionGranted = await location. requestPermission ();
if (permissionGranted != PermissionStatus .granted) {
locationData = await location. getLocation ();
final lat = locationData.latitude;
final lng = locationData.longitude;
if (lat == null || lng == null ) {
print ( 'Using Users current location' );
_userLocation = UserLocation (latitude : lat, longitude : lng);
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
Future < void > _fetchAddress ( double latitude, double longitude) async {
'https://maps.googleapis.com/maps/api/geocode/json?latlng=$ latitude ,$ longitude &key=$ _apiKey ' ,
final response = await http. get (url);
final resData = json. decode (response.body);
final address = resData[ 'results' ][ 0 ][ 'formatted_address' ];
_parsedAddress = address;
_isLoadingAddress = false ;
void _showInteractiveMap () async {
UserLocation ? selectedLocation =
await Navigator . of (context). push < UserLocation >(
builder : (ctx) => MapScreen (
if (selectedLocation != null ) {
_userLocation = selectedLocation;
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
Widget build ( BuildContext context) {
Widget addressValue = const CircularProgressIndicator ();
if ( ! _isLoadingAddress) {
color : Theme . of (context).colorScheme.onBackground,
mainAxisAlignment : MainAxisAlignment .center,
'Your current location is:' ,
style : TextStyle (color : Theme . of (context).colorScheme.onBackground),
decoration : BoxDecoration (
borderRadius : BorderRadius . circular ( 12 ),
color : Theme . of (context).colorScheme.primary. withOpacity ( 0.6 ),
alignment : Alignment .center,
child : const Text ( 'Soon' ),
icon : const Icon ( Icons .map),
label : const Text ( 'Show Map' ),
onPressed : _showInteractiveMap,
content = const Center (child : CircularProgressIndicator ());
title : const Text ( 'Google Maps Case' ),
body : SingleChildScrollView (
padding : const EdgeInsets . all ( 24 ),
After updating the home.dart
file, you should see a simple container with a border on the Home screen right above the Show Map
button.
Add Static Map Image
Now, let’s add the static map image to the Home screen to replace our placeholder.
import 'package:flutter/foundation.dart' ;
import 'package:flutter/material.dart' ;
import 'package:flutter_dotenv/flutter_dotenv.dart' ;
import 'package:google_maps_project/models/user_location.dart' ;
import 'package:google_maps_project/screens/map.dart' ;
import 'package:http/http.dart' as http;
import 'package:location/location.dart' ;
class HomeScreen extends StatefulWidget {
const HomeScreen ({ super .key});
State < HomeScreen > createState () {
return _HomeScreenState ();
class _HomeScreenState extends State < HomeScreen > {
final String _apiKey = dotenv. get ( 'GOOGLE_MAPS_API_KEY' );
UserLocation _userLocation =
const UserLocation (latitude : 37.422 , longitude : - 122.084 );
var _isLoadingAddress = false ;
var _parsedAddress = 'Unknown Address' ;
void _getCurrentLocation () async {
Location location = Location ();
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location. serviceEnabled ();
serviceEnabled = await location. requestService ();
permissionGranted = await location. hasPermission ();
if (permissionGranted == PermissionStatus .denied) {
permissionGranted = await location. requestPermission ();
if (permissionGranted != PermissionStatus .granted) {
locationData = await location. getLocation ();
final lat = locationData.latitude;
final lng = locationData.longitude;
if (lat == null || lng == null ) {
print ( 'Using Users current location' );
_userLocation = UserLocation (latitude : lat, longitude : lng);
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
Future < void > _fetchAddress ( double latitude, double longitude) async {
'https://maps.googleapis.com/maps/api/geocode/json?latlng=$ latitude ,$ longitude &key=$ _apiKey ' ,
final response = await http. get (url);
final resData = json. decode (response.body);
final address = resData[ 'results' ][ 0 ][ 'formatted_address' ];
_parsedAddress = address;
_isLoadingAddress = false ;
void _showInteractiveMap () async {
UserLocation ? selectedLocation =
await Navigator . of (context). push < UserLocation >(
builder : (ctx) => MapScreen (
if (selectedLocation != null ) {
_userLocation = selectedLocation;
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
String _createStaticMapUrl () {
double lat = _userLocation.latitude;
double lng = _userLocation.longitude;
return 'https://maps.googleapis.com/maps/api/staticmap?center=$ lat ,$ lng &zoom=16&size=600x300&maptype=roadmap&markers=color:red%7Clabel:1%7C$ lat ,$ lng &key=$ _apiKey ' ;
Widget build ( BuildContext context) {
Widget addressValue = const CircularProgressIndicator ();
if ( ! _isLoadingAddress) {
color : Theme . of (context).colorScheme.onBackground,
mainAxisAlignment : MainAxisAlignment .center,
'Your current location is:' ,
style : TextStyle (color : Theme . of (context).colorScheme.onBackground),
decoration : BoxDecoration (
borderRadius : BorderRadius . circular ( 12 ),
color : Theme . of (context).colorScheme.primary. withOpacity ( 0.6 ),
alignment : Alignment .center,
clipBehavior : Clip .antiAlias,
loadingBuilder : (ctx, child, loadingProgress) {
if (loadingProgress == null ) {
child : CircularProgressIndicator (),
icon : const Icon ( Icons .map),
label : const Text ( 'Show Map' ),
onPressed : _showInteractiveMap,
content = const Center (child : CircularProgressIndicator ());
title : const Text ( 'Google Maps Case' ),
body : SingleChildScrollView (
padding : const EdgeInsets . all ( 24 ),
Save your changes and you will see two updates in action: the static map image and the loading animation while the image is loading.
A different way - Journey
If you don’t want to use Google Maps API, you can use the alternative solution - Journey .
Journey is a simple and easy-to-use Flutter widget that provides flexible configurable static maps.
Note: this package is work in progress
and may have some limitations and can be changed in the future.
First, you need to request API key from Journey here: Journey - Request Access .
Follow the instructions to install the package and use it in your Flutter project.
It’s basic, install a package:
flutter pub add static_map
Add Journey’s API key to your .env
file: JOURNEY_MAPS_API_KEY=YOUR_API_KEY
.
Update main.dart
file to load the API key from the .env
file and initialize the Journey’s StaticMap
widget.
import 'package:flutter/material.dart' ;
import 'package:flutter_dotenv/flutter_dotenv.dart' ;
import 'package:google_maps_project/screens/home.dart' ;
import 'package:google_maps_project/theme.dart' ;
import 'package:static_map/static_map.dart' ;
WidgetsFlutterBinding . ensureInitialized ();
await dotenv. load (fileName : ".env" );
StaticMap . initialize (apiKey : dotenv. get ( 'JOURNEY_MAPS_API_KEY' ));
class MyApp extends StatelessWidget {
const MyApp ({ super .key});
// This widget is the root of your application.
Widget build ( BuildContext context) {
home : const HomeScreen (),
And now let’s move to update our home.dart
file to use Journey’s StaticMapImage
widget.
We will reuse Container
we built for Google Maps example and replace Image.network
with StaticMapImage
.
Check Basic Usage documentation for more details.
import 'package:flutter/foundation.dart' ;
import 'package:flutter/material.dart' ;
import 'package:flutter_dotenv/flutter_dotenv.dart' ;
import 'package:google_maps_project/models/user_location.dart' ;
import 'package:google_maps_project/screens/map.dart' ;
import 'package:http/http.dart' as http;
import 'package:location/location.dart' ;
import 'package:static_map/static_map.dart' ;
class HomeScreen extends StatefulWidget {
const HomeScreen ({ super .key});
State < HomeScreen > createState () {
return _HomeScreenState ();
class _HomeScreenState extends State < HomeScreen > {
final String _apiKey = dotenv. get ( 'GOOGLE_MAPS_API_KEY' );
UserLocation _userLocation =
const UserLocation (latitude : 37.422 , longitude : - 122.084 );
var _isLoadingAddress = false ;
var _parsedAddress = 'Unknown Address' ;
void _getCurrentLocation () async {
Location location = Location ();
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location. serviceEnabled ();
serviceEnabled = await location. requestService ();
permissionGranted = await location. hasPermission ();
if (permissionGranted == PermissionStatus .denied) {
permissionGranted = await location. requestPermission ();
if (permissionGranted != PermissionStatus .granted) {
locationData = await location. getLocation ();
final lat = locationData.latitude;
final lng = locationData.longitude;
if (lat == null || lng == null ) {
print ( 'Using Users current location' );
_userLocation = UserLocation (latitude : lat, longitude : lng);
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
Future < void > _fetchAddress ( double latitude, double longitude) async {
'https://maps.googleapis.com/maps/api/geocode/json?latlng=$ latitude ,$ longitude &key=$ _apiKey ' ,
final response = await http. get (url);
final resData = json. decode (response.body);
final address = resData[ 'results' ][ 0 ][ 'formatted_address' ];
_parsedAddress = address;
_isLoadingAddress = false ;
void _showInteractiveMap () async {
UserLocation ? selectedLocation =
await Navigator . of (context). push < UserLocation >(
builder : (ctx) => MapScreen (
if (selectedLocation != null ) {
_userLocation = selectedLocation;
_isLoadingAddress = true ;
_fetchAddress (_userLocation.latitude, _userLocation.longitude);
String _createStaticMapUrl () {
double lat = _userLocation.latitude;
double lng = _userLocation.longitude;
return 'https://maps.googleapis.com/maps/api/staticmap?center=$ lat ,$ lng &zoom=16&size=600x300&maptype=roadmap&markers=color:red%7Clabel:1%7C$ lat ,$ lng &key=$ _apiKey ' ;
Widget build ( BuildContext context) {
Widget addressValue = const CircularProgressIndicator ();
if ( ! _isLoadingAddress) {
color : Theme . of (context).colorScheme.onBackground,
mainAxisAlignment : MainAxisAlignment .center,
'Your current location is:' ,
style : TextStyle (color : Theme . of (context).colorScheme.onBackground),
decoration : BoxDecoration (
borderRadius : BorderRadius . circular ( 12 ),
color : Theme . of (context).colorScheme.primary. withOpacity ( 0.6 ),
alignment : Alignment .center,
clipBehavior : Clip .antiAlias,
loadingBuilder : (ctx, child, loadingProgress) {
if (loadingProgress == null ) {
child : CircularProgressIndicator (),
decoration : BoxDecoration (
borderRadius : BorderRadius . circular ( 12 ),
color : Theme . of (context).colorScheme.primary. withOpacity ( 0.6 ),
alignment : Alignment .center,
clipBehavior : Clip .antiAlias,
imageOptions : const ImageOptions (
loadingBuilder : (ctx, child, loadingProgress) {
if (loadingProgress == null ) {
child : CircularProgressIndicator (),
options : StaticMapOptions (
labelColor : Colors .black,
icon : const Icon ( Icons .map),
label : const Text ( 'Show Map' ),
onPressed : _showInteractiveMap,
content = const Center (child : CircularProgressIndicator ());
title : const Text ( 'Google Maps Case' ),
body : SingleChildScrollView (
padding : const EdgeInsets . all ( 24 ),
Save, refresh app, and you should see another static map image added below Google Maps Static Map image, but this one is generated by Journey’s StaticMapImage
widget.
Conclusion
In this post, we have learned how to show a static map in Flutter using Google Maps API.
We have also introduced an alternative solution - Journey, which provides a simple and easy-to-use Flutter widget for static maps.
I encourage you to play with different parameters and see how it affects the map image.
Thank you from reading and treat yourself with a good piece of cake 🍰