Working with a user’s location is a common task that many apps require, yet the steps to implement a well working solution in an app is often confusing and has too much boilerplate required to set up. Most Flutter packages abstract away most or all of the boilerplate required and directly give users the most optimised solution and less hassle to deal with. Often this means some fine grained control is lost through abstraction, but most times it is not needed anyway.
You can look through a tutorial for implementing location in native android to see the immense and often not needed amount of information presented to app developers who simply want the user’s location or want to track it.
Today, we’ll look through implementing getting location, location tracking and geocoding in Flutter for both Android and iOS using two different plugins which give us a varied amount of control over the underlying APIs and then see the implementation of location with maps where we’ll track a user using a marker on a map.
Let’s get started.
The ‘location’ plugin is the simplest way to get and track a user’s location without the hassle of any boilerplate code.
Setting up is simple enough:
pubspec.yaml
dependencies:
location: ^1.4.1
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Info.plist
<key>NSLocationAlwaysUsageDescription</key>
<string>Needed to access location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Needed to access location</string>
And you’re done.
In Android 6.0+, we need to ask access to location in runtime, which this package handles on its own.
It allows us to get two things:
Let’s create a simple app which gets the one-time location of the user like above.
Like we discussed, this package does not need much setup and the code is rather straightforward.
First, simply initialise a Location object.
var location = new Location();
location
is not the location of the user itself but a class that helps us get it.
The location is returned as a map with keys like “latitude”, “longitude”, etc.
Map<String, double> userLocation;
And finally, getting the location is as simple as:
userLocation = await location.getLocation();
That’s it.
For the complete code:
class GetLocationPage extends StatefulWidget {
@override
_GetLocationPageState createState() => _GetLocationPageState();
}
class _GetLocationPageState extends State<GetLocationPage> {
var location = new Location();
Map<String, double> userLocation;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
userLocation == null
? CircularProgressIndicator()
: Text("Location:" +
userLocation["latitude"].toString() +
" " +
userLocation["longitude"].toString()),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
_getLocation().then((value) {
setState(() {
userLocation = value;
});
});
},
color: Colors.blue,
child: Text("Get Location", style: TextStyle(color: Colors.white),),
),
),
],
),
),
);
}
Future<Map<String, double>> _getLocation() async {
var currentLocation = <String, double>{};
try {
currentLocation = await location.getLocation();
} catch (e) {
currentLocation = null;
}
return currentLocation;
}
}
GetLocationPage.dart
Here, we get the latitude and longitude. Other factors available are:
currentLocation["latitude"];
currentLocation["longitude"];
currentLocation["accuracy"];
currentLocation["altitude"];
currentLocation["speed"];
currentLocation["speed_accuracy"]; //Not for iOS
Listening to location updates is not that different from getting location, we simply use a stream object provided by the plugin to listen to updates.
location.onLocationChanged().listen((Map<String,double> currentLocation) {
print(currentLocation["latitude"]);
print(currentLocation["longitude"]);
print(currentLocation["accuracy"]);
print(currentLocation["altitude"]);
print(currentLocation["speed"]);
print(currentLocation["speed_accuracy"]);
});
An example page of this would be:
import 'package:flutter/material.dart';
import 'package:location/location.dart';
class ListenPage extends StatefulWidget {
@override
_ListenPageState createState() => _ListenPageState();
}
class _ListenPageState extends State<ListenPage> {
Location location = Location();
Map<String, double> currentLocation;
@override
void initState() {
// TODO: implement initState
super.initState();
location.onLocationChanged().listen((value) {
setState(() {
currentLocation = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
currentLocation == null
? CircularProgressIndicator()
: Text("Location:" + currentLocation["latitude"].toString() + " " + currentLocation["longitude"].toString()),
],
),
);
}
}
ListenPage.dart
Which gives a similar page to the one we made with the first example, but this time, we listen for location.
This plugin was a simple one to use without much setup or control and was straightforward in use and intention. Next, we’ll see another which gives more control over the APIs involved and also allows for geocoding.
The setup for this plugin is more or less the same as the location. If you plan to use coarse location as well, add
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
to AndroidManifest.xml
.
First, we will do the same two tasks with this plugin, which is more or less the same:
Use the Geolocator
object instead of Location
and instead of a Map<String,double>
, we get back a Position
object.
Geolocator geolocator = Geolocator();
Position userLocation;
And we do this to take location:
currentLocation = await geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.best);
Here, we can also set the location accuracy depending on the accuracy we need.
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'dart:async';
class GeoListenPage extends StatefulWidget {
@override
_GeoListenPageState createState() => _GeoListenPageState();
}
class _GeoListenPageState extends State<GeoListenPage> {
Geolocator geolocator = Geolocator();
Position userLocation;
@override
void initState() {
// TODO: implement initState
super.initState();
_getLocation().then((position) {
userLocation = position;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
userLocation == null
? CircularProgressIndicator()
: Text("Location:" +
userLocation.latitude.toString() +
" " +
userLocation.longitude.toString()),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
_getLocation().then((value) {
setState(() {
userLocation = value;
});
});
},
color: Colors.blue,
child: Text(
"Get Location",
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
);
}
Future<Position> _getLocation() async {
var currentLocation;
try {
currentLocation = await geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.best);
} catch (e) {
currentLocation = null;
}
return currentLocation;
}
}
GeolocatorListenPage.dart
Updates are similar to the ‘location’ package but with enhanced options.
geolocator.getPositionStream().listen((position) {
// Do something here
});
We can set the accuracy, time interval between updates, whether we want to force usage of Android location manager, etc.
geolocator
.getPositionStream(LocationOptions(
accuracy: LocationAccuracy.best, timeInterval: 1000))
.listen((position) {
// Do something here
});
Geocoding is essentially the process of getting physical map co-ordinates and details for an address.
List<Placemark> placemark = await Geolocator().placemarkFromAddress("Gronausestraat 710, Enschede");
You can get all the details about the place as:
print(placemark[0].country);
print(placemark[0].position);
print(placemark[0].locality);
print(placemark[0].administrativeArea);
print(placemark[0].postalCode);
print(placemark[0].name);
print(placemark[0].subAdministratieArea);
print(placemark[0].isoCountryCode);
print(placemark[0].subLocality);
print(placemark[0].subThoroughfare);
print(placemark[0].thoroughfare);
We can also get the details of a place from the coordinates:
List<Placemark> placemark = await Geolocator().placemarkFromCoordinates(52.2165157, 6.9437819);
This plugin also allows us to calculate the distance between two coordinates as:
double distanceInMeters = await Geolocator().distanceBetween(52.2165157, 6.9437819, 52.3546274, 4.8285838);
To integrate Google Maps with location updates, we need to use the google_maps_flutter package and your choice of location plugin.
Let’s just use the ‘location’ package for now.
A precursor to this part is my “Exploring Google Maps in Flutter” article and I highly recommend you use it to set up Maps in your app.
Exploring Google Maps in Flutter
To integrate maps with location tracking, we simply need to use both the plugins in conjunction.
Let’s move a marker as the user changes location.
First, we create our MapController
and Location
objects to manipulate maps and location. (Assign mapController
when map is initialised, as given in the upper article)
GoogleMapController mapController;
Location location = Location();
Next, we add a marker to store the current marker:
Marker marker;
Now, we listen to location updates.
location.onLocationChanged().listen((location) async {
// Do something here
});
This is how we will move the marker when location is updated:
location.onLocationChanged().listen((location) async {
// Step 1
if(marker != null) {
mapController.removeMarker(marker);
}
// Step 2
marker = await mapController?.addMarker(MarkerOptions(
position: LatLng(location["latitude"], location["longitude"]),
));
//Step 3
mapController?.moveCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
location["latitude"],
location["longitude"],
),
zoom: 20.0,
),
),
);
});
And now the marker will update location when the user moves!
Here’s the complete demo page code:
class GoogleMapsDemo extends StatefulWidget {
@override
_GoogleMapsDemoState createState() => _GoogleMapsDemoState();
}
class _GoogleMapsDemoState extends State<GoogleMapsDemo> {
GoogleMapController mapController;
Location location = Location();
Marker marker;
@override
void initState() {
super.initState();
location.onLocationChanged().listen((location) async {
if(marker != null) {
mapController.removeMarker(marker);
}
marker = await mapController?.addMarker(MarkerOptions(
position: LatLng(location["latitude"], location["longitude"]),
));
mapController?.moveCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
location["latitude"],
location["longitude"],
),
zoom: 20.0,
),
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
options: GoogleMapOptions(
cameraPosition: CameraPosition(
target: LatLng(37.4219999, -122.0862462),
),
myLocationEnabled: true,
),
),
),
],
),
);
}
}
GMapsPlusLocation.dart
That’s it for this article! I hope you enjoyed it, and leave a few claps if you did. Follow me for more Flutter articles and comment for any feedback you might have about this article.
#flutter #programming