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 {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Google Maps Demo',
home: GoogleMapsFlutter(),
);
}
}
class GoogleMapsFlutter extends StatefulWidget {
@override
_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,
);
@override
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 markers
parameter 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;
@override
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.
Top comments (0)