Introduction:
Integrating Google Maps into a Flutter app provides a powerful way to display geographic locations, routes, and interactive maps. In this step-by-step guide, we’ll walk through the process of implementing Google Maps in a Flutter app using the google_maps_flutter
package.
Content:
Step 1: Create a New Flutter Project
Ensure that you have Flutter installed and set up on your machine. Create a new Flutter project using the following command in your terminal:
flutter create google_maps_app
Step 2: Add the google_maps_flutter
Open the pubspec.yaml
file in your Flutter project and add the google_maps_flutter
dependency:
dependencies:
google_maps_flutter: ^2.0.14
Step 3: Set Up API Key
To use Google Maps, you’ll need to obtain an API key. Follow the official documentation to generate your API key.
Step 4: Enable Google Maps API
Enable the “Google Maps Android API” and “Google Maps JavaScript API” in the Google Cloud Console.
Step 5: Add required permissions in manifest
path:-android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.location.network" android:required="false" />
Step 6: Add required google dependancy in build.gradle
path:- android/buil.gradle
add this line in dependencies
classpath 'com.google.gms:google-services:4.3.15'
Step 7: Implement Google Maps in Flutter
Open the lib/main.dart
file and replace its content with the following code:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
void main() {
runApp(GoogleMapsApp());
}
class GoogleMapsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Google Maps Example"),
),
body: GoogleMapWidget(),
),
);
}
}
class GoogleMapWidget extends StatefulWidget {
@override
_GoogleMapWidgetState createState() => _GoogleMapWidgetState();
}
class _GoogleMapWidgetState extends State<GoogleMapWidget> {
late GoogleMapController mapController;
@override
Widget build(BuildContext context) {
return GoogleMap(
onMapCreated: (controller) {
setState(() {
mapController = controller;
});
},
initialCameraPosition: CameraPosition(
target: LatLng(37.7749, -122.4194), // San Francisco, CA coordinates
zoom: 12,
),
);
}
}
Step 8: Run the App
Save your changes and run the app using the following command in your terminal:
flutter run
Sample Code:
// ignore_for_file: prefer_const_constructors, sort_child_properties_last
import 'package:flutter/material.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:flutter_videos/constants/colors_constant.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:math' show cos, sqrt, asin;
class MapNavigateToPlaces extends StatefulWidget {
const MapNavigateToPlaces({Key? key}) : super(key: key);
@override
State<MapNavigateToPlaces> createState() => _MapNavigateToPlacesState();
}
class _MapNavigateToPlacesState extends State<MapNavigateToPlaces> {
final CameraPosition _initialLocation = const CameraPosition(target: LatLng(0.0, 0.0));
late GoogleMapController mapController;
late Position _currentPosition;
String _currentAddress = '';
final startAddressController = TextEditingController();
final destinationAddressController = TextEditingController();
final startAddressFocusNode = FocusNode();
final desrinationAddressFocusNode = FocusNode();
String _startAddress = '';
String _destinationAddress = '';
String? _placeDistance;
Set<Marker> markers = {};
late PolylinePoints polylinePoints;
Map<PolylineId, Polyline> polylines = {};
List<LatLng> polylineCoordinates = [];
Widget _textField({
required TextEditingController controller,
required FocusNode focusNode,
// required String label,
required String hint,
required double width,
// required Icon prefixIcon,
Widget? suffixIcon,
required Function(String) locationCallback,
}) {
return Container(
width: width * 0.8,
child: TextField(
onChanged: (value) {
locationCallback(value);
},
controller: controller,
focusNode: focusNode,
decoration: InputDecoration(
// prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
// labelText: label,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.grey.shade400,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.blue.shade300,
width: 2,
),
),
contentPadding: EdgeInsets.all(15),
hintText: hint,
),
),
);
}
// Method for retrieving the current location
_getCurrentLocation() async {
await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high).then((Position position) async {
setState(() {
_currentPosition = position;
print('CURRENT POS: $_currentPosition');
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(position.latitude, position.longitude),
zoom: 18.0,
),
),
);
});
await _getAddress();
}).catchError((e) {
print(e);
});
}
// Method for retrieving the address
_getAddress() async {
try {
List<Placemark> p = await placemarkFromCoordinates(_currentPosition.latitude, _currentPosition.longitude);
Placemark place = p[0];
setState(() {
_currentAddress = "${place.name}, ${place.locality}, ${place.postalCode}, ${place.country}";
startAddressController.text = _currentAddress;
_startAddress = _currentAddress;
});
} catch (e) {
print(e);
}
}
// Method for calculating the distance between two places
Future<bool> _calculateDistance() async {
try {
// Retrieving placemarks from addresses
List<Location> startPlacemark = await locationFromAddress(_startAddress);
List<Location> destinationPlacemark = await locationFromAddress(_destinationAddress);
// Use the retrieved coordinates of the current position,
// instead of the address if the start position is user's
// current position, as it results in better accuracy.
double startLatitude = _startAddress == _currentAddress ? _currentPosition.latitude : startPlacemark[0].latitude;
double startLongitude = _startAddress == _currentAddress ? _currentPosition.longitude : startPlacemark[0].longitude;
double destinationLatitude = destinationPlacemark[0].latitude;
double destinationLongitude = destinationPlacemark[0].longitude;
String startCoordinatesString = '($startLatitude, $startLongitude)';
String destinationCoordinatesString = '($destinationLatitude, $destinationLongitude)';
// Start Location Marker
Marker startMarker = Marker(
markerId: MarkerId(startCoordinatesString),
position: LatLng(startLatitude, startLongitude),
infoWindow: InfoWindow(
title: 'Start $startCoordinatesString',
snippet: _startAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Destination Location Marker
Marker destinationMarker = Marker(
markerId: MarkerId(destinationCoordinatesString),
position: LatLng(destinationLatitude, destinationLongitude),
infoWindow: InfoWindow(
title: 'Destination $destinationCoordinatesString',
snippet: _destinationAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Adding the markers to the list
markers.add(startMarker);
markers.add(destinationMarker);
print(
'START COORDINATES: ($startLatitude, $startLongitude)',
);
print(
'DESTINATION COORDINATES: ($destinationLatitude, $destinationLongitude)',
);
// Calculating to check that the position relative
// to the frame, and pan & zoom the camera accordingly.
double miny = (startLatitude <= destinationLatitude) ? startLatitude : destinationLatitude;
double minx = (startLongitude <= destinationLongitude) ? startLongitude : destinationLongitude;
double maxy = (startLatitude <= destinationLatitude) ? destinationLatitude : startLatitude;
double maxx = (startLongitude <= destinationLongitude) ? destinationLongitude : startLongitude;
double southWestLatitude = miny;
double southWestLongitude = minx;
double northEastLatitude = maxy;
double northEastLongitude = maxx;
// Accommodate the two locations within the
// camera view of the map
mapController.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
northeast: LatLng(northEastLatitude, northEastLongitude),
southwest: LatLng(southWestLatitude, southWestLongitude),
),
100.0,
),
);
// Calculating the distance between the start and the end positions
// with a straight path, without considering any route
// double distanceInMeters = await Geolocator.bearingBetween(
// startLatitude,
// startLongitude,
// destinationLatitude,
// destinationLongitude,
// );
await _createPolylines(startLatitude, startLongitude, destinationLatitude, destinationLongitude);
double totalDistance = 0.0;
// Calculating the total distance by adding the distance
// between small segments
for (int i = 0; i < polylineCoordinates.length - 1; i++) {
totalDistance += _coordinateDistance(
polylineCoordinates[i].latitude,
polylineCoordinates[i].longitude,
polylineCoordinates[i + 1].latitude,
polylineCoordinates[i + 1].longitude,
);
}
setState(() {
_placeDistance = totalDistance.toStringAsFixed(2);
print('DISTANCE: $_placeDistance km');
});
return true;
} catch (e) {
print(e);
}
return false;
}
// Formula for calculating distance between two coordinates
// https://stackoverflow.com/a/54138876/11910277
double _coordinateDistance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 - c((lat2 - lat1) * p) / 2 + c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
// Create the polylines for showing the route between two places
_createPolylines(
double startLatitude,
double startLongitude,
double destinationLatitude,
double destinationLongitude,
) async {
polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
'AIzaSyDMl9apJLo68hKK9qews_Wu-kIDkz4UW-A', // Google Maps API Key
PointLatLng(startLatitude, startLongitude),
PointLatLng(destinationLatitude, destinationLongitude),
travelMode: TravelMode.transit,
);
if (result.points.isNotEmpty) {
result.points.forEach((PointLatLng point) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
});
}
PolylineId id = PolylineId('poly');
Polyline polyline = Polyline(
polylineId: id,
color: Colors.red,
points: polylineCoordinates,
width: 3,
);
polylines[id] = polyline;
}
@override
void initState() {
super.initState();
_getCurrentLocation();
}
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Scaffold(
body: Stack(
children: <Widget>[
// Map View
GoogleMap(
markers: Set<Marker>.from(markers),
initialCameraPosition: _initialLocation,
myLocationEnabled: true,
myLocationButtonEnabled: false,
mapType: MapType.normal,
zoomGesturesEnabled: true,
zoomControlsEnabled: false,
polylines: Set<Polyline>.of(polylines.values),
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
),
// Show zoom buttons
SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
splashColor: Colors.blue, // inkwell color
child: const SizedBox(
width: 40,
height: 40,
child: Icon(Icons.add),
),
onTap: () {
mapController.animateCamera(
CameraUpdate.zoomIn(),
);
},
),
),
),
SizedBox(height: 20),
ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
splashColor: Colors.blue, // inkwell color
child: SizedBox(
width: 40,
height: 40,
child: Icon(Icons.remove),
),
onTap: () {
mapController.animateCamera(
CameraUpdate.zoomOut(),
);
},
),
),
)
],
),
),
),
// Show the place input fields & button for
// showing the route
SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
),
// width: width * 0.9,
child: Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Text(
'Starting point',
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w600),
),
),
],
),
SizedBox(height: 5),
_textField(
// label: 'Start',
hint: 'Choose starting point',
// prefixIcon: Icon(Icons.looks_one),
suffixIcon: IconButton(
icon: Icon(Icons.my_location),
onPressed: () {
startAddressController.text = _currentAddress;
_startAddress = _currentAddress;
},
),
controller: startAddressController,
focusNode: startAddressFocusNode,
width: width,
locationCallback: (String value) {
setState(() {
_startAddress = value;
});
}),
SizedBox(height: 10),
Row(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Text(
'Destination point',
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w600),
),
),
],
),
SizedBox(height: 5),
_textField(
// label: 'Destination',
hint: 'Choose destination',
// prefixIcon: Icon(Icons.looks_two),
controller: destinationAddressController,
focusNode: desrinationAddressFocusNode,
width: width,
locationCallback: (String value) {
setState(() {
_destinationAddress = value;
});
}),
SizedBox(height: 15),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 15),
decoration: BoxDecoration(color: Colors.white70, borderRadius: BorderRadius.circular(8)),
child: Visibility(
visible: _placeDistance == null ? false : true,
child: Text(
'DIST: $_placeDistance km',
style: TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
),
ElevatedButton(
onPressed: (_startAddress != '' && _destinationAddress != '')
? () async {
startAddressFocusNode.unfocus();
desrinationAddressFocusNode.unfocus();
setState(() {
if (markers.isNotEmpty) markers.clear();
if (polylines.isNotEmpty) polylines.clear();
if (polylineCoordinates.isNotEmpty) polylineCoordinates.clear();
_placeDistance = null;
});
_calculateDistance().then((isCalculated) {
if (isCalculated) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Distance Calculated Sucessfully'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error Calculating Distance'),
),
);
}
});
}
: null,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
'Direction'.toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
style: ElevatedButton.styleFrom(
primary: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
],
),
SizedBox(
height: 10,
)
],
),
),
),
),
),
),
// Show current location button
SafeArea(
child: Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(right: 10.0, bottom: 10.0),
child: ClipOval(
child: Material(
color: Colors.orange.shade100, // button color
child: InkWell(
splashColor: Colors.orange, // inkwell color
child: SizedBox(
width: 56,
height: 56,
child: Icon(Icons.my_location),
),
onTap: () {
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
_currentPosition.latitude,
_currentPosition.longitude,
),
zoom: 18.0,
),
),
);
},
),
),
),
),
),
),
],
),
);
}
}
Output: