Location services and maps are fundamental components in many modern mobile applications. Integrating these features into your Flutter apps can enhance user experience, enabling location-based services such as navigation, location sharing, and nearby place discovery. This comprehensive guide will walk you through the process of working with location services and maps in Flutter, providing detailed code examples and explanations.
Setting Up Your Flutter Project
Before diving into the code, make sure you have a Flutter project set up. If not, create a new Flutter project using the following command:
flutter create location_map_app
Next, navigate to your project directory:
cd location_map_app
Adding Dependencies
To work with location services and maps, you need to add the necessary dependencies to your pubspec.yaml file. Here are the packages we’ll use:
- geolocator: For accessing platform-specific location services.
- google_maps_flutter: For displaying Google Maps in your Flutter app.
Add these dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
geolocator: ^9.0.2
google_maps_flutter: ^2.2.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
After adding the dependencies, run the following command to fetch the packages:
flutter pub get
Platform-Specific Setup
Before using these packages, you need to configure platform-specific settings for both Android and iOS.
Android Setup
Step 1: Obtain an API Key
To use Google Maps on Android, you need an API key. Follow these steps to obtain one:
- Go to the Google Cloud Console.
- Create a new project or select an existing one.
- Enable the “Maps SDK for Android.”
- Create an API key and restrict it to Android apps using your app’s package name and SHA-1 signing certificate fingerprint.
Step 2: Configure AndroidManifest.xml
Open the android/app/src/main/AndroidManifest.xml file and add the following permissions before the <application> tag:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Add the following meta-data tag inside the <application> tag, replacing YOUR_API_KEY with your actual API key:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY" />
Also, ensure that your application targets at least SDK version 21. Modify the android/app/build.gradle file:
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.example.location_map_app"
minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
}
iOS Setup
Step 1: Obtain an API Key
Similar to Android, you need an API key for Google Maps on iOS. Follow the same steps as above to obtain an API key, but this time, restrict it to iOS apps using your app’s bundle identifier.
Step 2: Configure Info.plist
Open the ios/Runner/Info.plist file and add the following keys:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location when open to provide location-based services.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location in the background to provide continuous location-based services.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to your location at all times to provide comprehensive location-based services.</string>
Add the following meta-data tag to your ios/Runner/AppDelegate.swift file within the didFinishLaunchingWithOptions method before the return super.application(...) line.
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR_API_KEY")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Requesting Location Permissions
Before accessing the user’s location, you must request the necessary permissions. Here’s how to do it using the geolocator package:
import 'package:geolocator/geolocator.dart';
Future<Position> determinePosition() async {
LocationPermission permission;
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
return await Geolocator.getCurrentPosition();
}
This function checks for location permissions and requests them if they haven’t been granted. It then retrieves the current position. Add error handling to manage cases where permissions are denied.
Displaying the Map
Now that you have the user’s location, you can display it on a Google Map. Here’s how to integrate google_maps_flutter to show a map centered on the user’s location:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapScreen extends StatefulWidget {
@override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
LatLng? _currentPosition;
Completer<GoogleMapController> _controller = Completer();
@override
void initState() {
super.initState();
_getCurrentLocation();
}
_getCurrentLocation() async {
Position position = await determinePosition();
setState(() {
_currentPosition = LatLng(position.latitude, position.longitude);
});
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(
target: _currentPosition!,
zoom: 15,
),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Maps'),
),
body: _currentPosition == null
? Center(child: CircularProgressIndicator())
: GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: CameraPosition(
target: _currentPosition!,
zoom: 12,
),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
markers: {
Marker(
markerId: MarkerId('currentLocation'),
position: _currentPosition!,
infoWindow: InfoWindow(title: 'Current Location'),
),
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _goToTheLake,
label: const Text('To the lake!'),
icon: const Icon(Icons.directions_boat),
),
);
}
Future<void> _goToTheLake() async {
final GoogleMapController controller = await _controller.future;
await controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
}
static const CameraPosition _kLake = CameraPosition(
bearing: 192.8334901395799,
target: LatLng(37.43296265331129, -122.08832357078792),
tilt: 59.44071769339172,
zoom: 19.151926040696805);
}
This code initializes a Google Map centered on the user’s current location. It also adds a marker to indicate the current position. Add a loading indicator while the location is being fetched and implement error handling to manage cases where location retrieval fails.
Advanced Features
To make your map more interactive, you can add advanced features like:
- Custom Markers: Replace the default marker with a custom image or icon.
- Polylines and Polygons: Draw routes and areas on the map.
- Map Styles: Customize the map’s appearance using JSON styles.
- Geocoding and Reverse Geocoding: Convert addresses to coordinates and vice versa.
Geocoding and Reverse Geocoding
Geocoding is the process of converting addresses into geographic coordinates (latitude and longitude), which you can use to display markers on a map. Reverse geocoding, on the other hand, converts geographic coordinates back into a human-readable address.
You can achieve these functionalities using the geocoding plugin along with geolocator to retrieve place marks. Here’s an example of how to geocode an address in Flutter using the geocoding plugin:
import 'package:geocoding/geocoding.dart';
Future<LatLng?> getLocationFromAddress(String address) async {
try {
List<Location> locations = await locationFromAddress(address);
if (locations.isNotEmpty) {
return LatLng(locations.first.latitude, locations.first.longitude);
}
return null;
} catch (e) {
print('Error during geocoding: $e');
return null;
}
}
Future<String?> getAddressFromLocation(LatLng position) async {
try {
List<Placemark> placemarks = await placemarkFromCoordinates(
position.latitude, position.longitude);
if (placemarks.isNotEmpty) {
Placemark place = placemarks[0];
return '${place.street}, ${place.locality}, ${place.postalCode}, ${place.country}';
}
return null;
} catch (e) {
print('Error during reverse geocoding: $e');
return null;
}
}
Update pubspec.yaml to use geocoding as a plugin by adding geocoding: ^2.0.0 inside of the dependecies.
Conclusion
Integrating location services and maps in your Flutter applications involves several steps, from setting up dependencies to requesting permissions and displaying maps. By following this guide and using the provided code examples, you can create feature-rich, location-aware apps that provide great value to your users.