Skip to content

Flutter Google Maps - Static Map

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.

lib/screens/home.dart
129 collapsed lines
import 'dart:convert';
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});
@override
State<HomeScreen> createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
var _isLoading = false;
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';
@override
void initState() {
super.initState();
_getCurrentLocation();
}
void _getCurrentLocation() async {
setState(() {
_isLoading = true;
});
Location location = Location();
bool serviceEnabled;
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
setState(() {
_isLoading = false;
});
return;
}
}
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
setState(() {
_isLoading = false;
});
return;
}
}
locationData = await location.getLocation();
final lat = locationData.latitude;
final lng = locationData.longitude;
if (lat == null || lng == null) {
setState(() {
_isLoading = false;
});
return;
}
if (kDebugMode) {
print('Using Users current location');
}
_userLocation = UserLocation(latitude: lat, longitude: lng);
setState(() {
_isLoading = false;
_isLoadingAddress = true;
});
_fetchAddress(_userLocation.latitude, _userLocation.longitude);
}
Future<void> _fetchAddress(double latitude, double longitude) async {
final url = Uri.parse(
'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'];
setState(() {
_parsedAddress = address;
_isLoadingAddress = false;
});
}
void _showInteractiveMap() async {
UserLocation? selectedLocation =
await Navigator.of(context).push<UserLocation>(
MaterialPageRoute(
builder: (ctx) => MapScreen(
location: _userLocation,
),
),
);
if (selectedLocation != null) {
setState(() {
_userLocation = selectedLocation;
_isLoadingAddress = true;
});
}
_fetchAddress(_userLocation.latitude, _userLocation.longitude);
}
@override
Widget build(BuildContext context) {
12 collapsed lines
Widget addressValue = const CircularProgressIndicator();
if (!_isLoadingAddress) {
addressValue = Text(
_parsedAddress,
style: TextStyle(
color: Theme.of(context).colorScheme.onBackground,
fontSize: 24,
),
);
}
Widget content = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
8 collapsed lines
Text(
'Your current location is:',
style: TextStyle(color: Theme.of(context).colorScheme.onBackground),
),
addressValue,
const SizedBox(
height: 16,
),
Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
width: 2,
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
),
),
alignment: Alignment.center,
child: const Text('Soon'),
),
const SizedBox(
height: 16,
),
14 collapsed lines
Center(
child: TextButton.icon(
icon: const Icon(Icons.map),
label: const Text('Show Map'),
onPressed: _showInteractiveMap,
),
),
],
);
if (_isLoading) {
content = const Center(child: CircularProgressIndicator());
}
return Scaffold(
appBar: AppBar(
title: const Text('Google Maps Case'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: content,
),
3 collapsed lines
);
}
}

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.

lib/screens/home.dart
128 collapsed lines
import 'dart:convert';
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});
@override
State<HomeScreen> createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
var _isLoading = false;
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';
@override
void initState() {
super.initState();
_getCurrentLocation();
}
void _getCurrentLocation() async {
setState(() {
_isLoading = true;
});
Location location = Location();
bool serviceEnabled;
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
setState(() {
_isLoading = false;
});
return;
}
}
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
setState(() {
_isLoading = false;
});
return;
}
}
locationData = await location.getLocation();
final lat = locationData.latitude;
final lng = locationData.longitude;
if (lat == null || lng == null) {
setState(() {
_isLoading = false;
});
return;
}
if (kDebugMode) {
print('Using Users current location');
}
_userLocation = UserLocation(latitude: lat, longitude: lng);
setState(() {
_isLoading = false;
_isLoadingAddress = true;
});
_fetchAddress(_userLocation.latitude, _userLocation.longitude);
}
Future<void> _fetchAddress(double latitude, double longitude) async {
final url = Uri.parse(
'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'];
setState(() {
_parsedAddress = address;
_isLoadingAddress = false;
});
}
void _showInteractiveMap() async {
UserLocation? selectedLocation =
await Navigator.of(context).push<UserLocation>(
MaterialPageRoute(
builder: (ctx) => MapScreen(
location: _userLocation,
),
),
);
if (selectedLocation != null) {
setState(() {
_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';
}
@override
Widget build(BuildContext context) {
23 collapsed lines
Widget addressValue = const CircularProgressIndicator();
if (!_isLoadingAddress) {
addressValue = Text(
_parsedAddress,
style: TextStyle(
color: Theme.of(context).colorScheme.onBackground,
fontSize: 24,
),
);
}
Widget content = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Your current location is:',
style: TextStyle(color: Theme.of(context).colorScheme.onBackground),
),
addressValue,
const SizedBox(
height: 16,
),
Container(
height: 200,
width: double.infinity,
7 collapsed lines
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
width: 2,
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
),
),
alignment: Alignment.center,
clipBehavior: Clip.antiAlias,
child: Image.network(
_createStaticMapUrl(),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
loadingBuilder: (ctx, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
),
28 collapsed lines
const SizedBox(
height: 16,
),
Center(
child: TextButton.icon(
icon: const Icon(Icons.map),
label: const Text('Show Map'),
onPressed: _showInteractiveMap,
),
),
],
);
if (_isLoading) {
content = const Center(child: CircularProgressIndicator());
}
return Scaffold(
appBar: AppBar(
title: const Text('Google Maps Case'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: content,
),
);
}
}

Save your changes and you will see two updates in action: the static map image and the loading animation while the image is loading.

Animation while image is loading
Static Map image loaded

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:

Terminal window
1
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.

lib/main.dart
6 collapsed lines
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';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
StaticMap.initialize(apiKey: dotenv.get('JOURNEY_MAPS_API_KEY'));
runApp(const MyApp());
}
14 collapsed lines
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: defaultTheme,
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.

lib/screens/home.dart
191 collapsed lines
1
import 'dart:convert';
2
3
import 'package:flutter/foundation.dart';
4
import 'package:flutter/material.dart';
5
import 'package:flutter_dotenv/flutter_dotenv.dart';
6
import 'package:google_maps_project/models/user_location.dart';
7
import 'package:google_maps_project/screens/map.dart';
8
import 'package:http/http.dart' as http;
9
import 'package:location/location.dart';
10
import 'package:static_map/static_map.dart';
11
12
class HomeScreen extends StatefulWidget {
13
const HomeScreen({super.key});
14
15
@override
16
State<HomeScreen> createState() {
17
return _HomeScreenState();
18
}
19
}
20
21
class _HomeScreenState extends State<HomeScreen> {
22
var _isLoading = false;
23
final String _apiKey = dotenv.get('GOOGLE_MAPS_API_KEY');
24
UserLocation _userLocation =
25
const UserLocation(latitude: 37.422, longitude: -122.084);
26
var _isLoadingAddress = false;
27
var _parsedAddress = 'Unknown Address';
28
29
@override
30
void initState() {
31
super.initState();
32
_getCurrentLocation();
33
}
34
35
void _getCurrentLocation() async {
36
setState(() {
37
_isLoading = true;
38
});
39
40
Location location = Location();
41
42
bool serviceEnabled;
43
PermissionStatus permissionGranted;
44
LocationData locationData;
45
46
serviceEnabled = await location.serviceEnabled();
47
48
if (!serviceEnabled) {
49
serviceEnabled = await location.requestService();
50
if (!serviceEnabled) {
51
setState(() {
52
_isLoading = false;
53
});
54
return;
55
}
56
}
57
58
permissionGranted = await location.hasPermission();
59
60
if (permissionGranted == PermissionStatus.denied) {
61
permissionGranted = await location.requestPermission();
62
if (permissionGranted != PermissionStatus.granted) {
63
setState(() {
64
_isLoading = false;
65
});
66
return;
67
}
68
}
69
70
locationData = await location.getLocation();
71
72
final lat = locationData.latitude;
73
final lng = locationData.longitude;
74
75
if (lat == null || lng == null) {
76
setState(() {
77
_isLoading = false;
78
});
79
return;
80
}
81
82
if (kDebugMode) {
83
print('Using Users current location');
84
}
85
86
_userLocation = UserLocation(latitude: lat, longitude: lng);
87
setState(() {
88
_isLoading = false;
89
_isLoadingAddress = true;
90
});
91
92
_fetchAddress(_userLocation.latitude, _userLocation.longitude);
93
}
94
95
Future<void> _fetchAddress(double latitude, double longitude) async {
96
final url = Uri.parse(
97
'https://maps.googleapis.com/maps/api/geocode/json?latlng=$latitude,$longitude&key=$_apiKey',
98
);
99
100
final response = await http.get(url);
101
final resData = json.decode(response.body);
102
final address = resData['results'][0]['formatted_address'];
103
104
setState(() {
105
_parsedAddress = address;
106
_isLoadingAddress = false;
107
});
108
}
109
110
void _showInteractiveMap() async {
111
UserLocation? selectedLocation =
112
await Navigator.of(context).push<UserLocation>(
113
MaterialPageRoute(
114
builder: (ctx) => MapScreen(
115
location: _userLocation,
116
),
117
),
118
);
119
120
if (selectedLocation != null) {
121
setState(() {
122
_userLocation = selectedLocation;
123
_isLoadingAddress = true;
124
});
125
}
126
127
_fetchAddress(_userLocation.latitude, _userLocation.longitude);
128
}
129
130
String _createStaticMapUrl() {
131
double lat = _userLocation.latitude;
132
double lng = _userLocation.longitude;
133
134
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';
135
}
136
137
@override
138
Widget build(BuildContext context) {
139
Widget addressValue = const CircularProgressIndicator();
140
141
if (!_isLoadingAddress) {
142
addressValue = Text(
143
_parsedAddress,
144
style: TextStyle(
145
color: Theme.of(context).colorScheme.onBackground,
146
fontSize: 24,
147
),
148
);
149
}
150
151
Widget content = Column(
152
mainAxisAlignment: MainAxisAlignment.center,
153
children: [
154
Text(
155
'Your current location is:',
156
style: TextStyle(color: Theme.of(context).colorScheme.onBackground),
157
),
158
addressValue,
159
const SizedBox(
160
height: 16,
161
),
162
Container(
163
height: 200,
164
width: double.infinity,
165
decoration: BoxDecoration(
166
borderRadius: BorderRadius.circular(12),
167
border: Border.all(
168
width: 2,
169
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
170
),
171
),
172
alignment: Alignment.center,
173
clipBehavior: Clip.antiAlias,
174
child: Image.network(
175
_createStaticMapUrl(),
176
fit: BoxFit.cover,
177
width: double.infinity,
178
height: double.infinity,
179
loadingBuilder: (ctx, child, loadingProgress) {
180
if (loadingProgress == null) {
181
return child;
182
}
183
184
return const Center(
185
child: CircularProgressIndicator(),
186
);
187
},
188
),
189
),
190
const SizedBox(
191
height: 16,
192
),
193
Container(
11 collapsed lines
194
height: 200,
195
width: double.infinity,
196
decoration: BoxDecoration(
197
borderRadius: BorderRadius.circular(12),
198
border: Border.all(
199
width: 2,
200
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
201
),
202
),
203
alignment: Alignment.center,
204
clipBehavior: Clip.antiAlias,
205
206
child: StaticMapImage(
207
208
imageOptions: const ImageOptions(
209
fit: BoxFit.cover,
210
),
211
212
loadingBuilder: (ctx, child, loadingProgress) {
213
if (loadingProgress == null) {
214
return child;
215
}
216
217
return const Center(
218
child: CircularProgressIndicator(),
219
);
220
},
221
222
options: StaticMapOptions(
223
center: StaticMapLatLng(
224
_userLocation.latitude,
225
_userLocation.longitude,
226
),
227
zoom: 16,
228
width: 600,
229
height: 300,
230
scale: 2,
231
overlays: [
232
233
StaticMapMarker(
234
point: StaticMapLatLng(
235
_userLocation.latitude,
236
_userLocation.longitude,
237
),
238
color: Colors.red,
239
size: 8,
240
label: 'A',
241
labelSize: 14,
242
labelColor: Colors.black,
243
)
244
],
245
),
246
),
247
),
28 collapsed lines
248
const SizedBox(
249
height: 16,
250
),
251
Center(
252
child: TextButton.icon(
253
icon: const Icon(Icons.map),
254
label: const Text('Show Map'),
255
onPressed: _showInteractiveMap,
256
),
257
),
258
],
259
);
260
261
if (_isLoading) {
262
content = const Center(child: CircularProgressIndicator());
263
}
264
265
return Scaffold(
266
appBar: AppBar(
267
title: const Text('Google Maps Case'),
268
),
269
body: SingleChildScrollView(
270
padding: const EdgeInsets.all(24),
271
child: content,
272
),
273
);
274
}
275
}

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.

Animation while Journey image is loading
Journey Static Map image loaded

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 🍰