A Flutter plugin for updating location even if the app is killed
-
The plugins register an Isolate as START_STICKY so it can't be killed by Android
-
It will execute a
static
callback function in your dart code where you execute everything except any UI operation (static
because it can't be accessed otherwise π) -
To change any UI element, you need to listen to the Isolate port that the callback function will send to
More info on Isolate : Isolates and Event Loops - Flutter in Focus
At every callback, append the location in a file, on app start, recover the position list from this file and put it on the map as a polyline
Add this to your package's pubspec.yaml file:
dependencies:
background_locator: ^1.1.2+1
You can remove ^1.1.2+1 to always get the latest version
You can install packages from the command line:
with Flutter:
$ flutter pub get
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:background_locator/background_locator.dart';
- Make sure to have the following permissions inside your
AndroidManifest.xml
:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- ACCESS_FINE_LOCATION to have the GPS π°οΈ
Note: you can set ACCESS_COARSE_LOCATION but you will have to modify the accuracy below
LocationAccuracy.LOW
- ACCESS_BACKGROUND_LOCATION To get update in background β
- WAKE_LOCK to not sleep while getting the GPS π΄
- FOREGROUND_SERVICE To let the plugins operate as a service βοΈ
- Add also the following lines to your
AndroidManifest.xml
to give the plugins's services their liberties:
<!-- If you want to get update when the app is in background
but not when it is killed you can add android:stopWithTask="true"
to these tree services -->
<receiver android:name="rekab.app.background_locator.LocatorBroadcastReceiver"
android:enabled="true"
android:exported="true"/>
<service android:name="rekab.app.background_locator.LocatorService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
<service android:name="rekab.app.background_locator.IsolateHolderService"
android:permission="android.permission.FOREGROUND_SERVICE"
android:exported="true"/>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
- To work with other plugins, even when the application is killed (using
path_provider
for example), create a new file insideandroid/app/src/main/kotlin/com/example/YourFlutterApp/
namedLocationService.kt
and fill it with :
package rekab.app.background_locator_example
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.pathprovider.PathProviderPlugin
import io.flutter.view.FlutterMain
import rekab.app.background_locator.LocatorService
class LocationService : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
LocatorService.setPluginRegistrant(this)
FlutterMain.startInitialization(this)
}
override fun registerWith(registry: PluginRegistry?) {
if (!registry!!.hasPlugin("io.flutter.plugins.pathprovider")) {
PathProviderPlugin.registerWith(registry!!.registrarFor("io.flutter.plugins.pathprovider"))
}
}
}
- Again, inside your
AndroidManifest.xml
changeandroid:name
fromio.flutter.app.FlutterApplication
torekab.app.background_locator_example.LocationService
to register the step 3:
<application
android:name="rekab.app.background_locator_example.LocationService"
Great ! π Its now your turn, inside your dart file create a callback function that the plugin will call in background
import 'package:path_provider/path_provider.dart';
static void callback(LocationDto locationDto) async {
print('location in dart: ${locationDto.toString()}');
final SendPort send = IsolateNameServer.lookupPortByName(_isolateName);
send?.send(locationDto);
//the '?' check if send is null before executing it
final file = await _getTempLogFile();
await file.writeAsString(locationDto.toString(), mode: FileMode.append);
}
- Add the following lines to
AppDelegate
class:
import background_locator
func registerPlugins(registry: FlutterPluginRegistry) -> () {
GeneratedPluginRegistrant.register(with: registry)
}
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
BackgroundLocatorPlugin.setPluginRegistrantCallback(registerPlugins)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
-
In app settings enable
Background Modes
and checkLocation Updates
. -
In
Info.plist
add Key for using location service:
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationWhenInUseUsageDescription
See the best practices here : Example app
- Initialize plugin:
static const String _isolateName = "LocatorIsolate";
ReceivePort port = ReceivePort();
@override
void initState() {
super.initState();
//override the previous port by this one, we can't
//re-use the previous port
if (IsolateNameServer.lookupPortByName(_isolateName) != null) {
IsolateNameServer.removePortNameMapping(_isolateName);
}
IsolateNameServer.registerPortWithName(port.sendPort, _isolateName);
port.listen((dynamic data) {
// do something with data
});
initPlatformState();
}
Future<void> initPlatformState() async {
await BackgroundLocator.initialize();
}
- Create the callback function, Also the notification callback if you need it:
static void callback(LocationDto locationDto) async {
final SendPort send = IsolateNameServer.lookupPortByName(_isolateName);
send?.send(locationDto);
}
//Optional
static void notificationCallback() {
print('User clicked on the notification');
}
- Start the location service :
Before starting the plugin make sure to have the necessary permissions
//Somewhere in your code
startLocationService();
void startLocationService(){
BackgroundLocator.registerLocationUpdate(
callback,
//optional
androidNotificationCallback: notificationCallback,
settings: LocationSettings(
//Scroll down to see the different options
notificationTitle: "Start Location Tracking example",
notificationMsg: "Track location in background exapmle",
wakeLockTime: 20,
autoStop: false,
interval: 1
),
);
}
- Unregister the service when you are done:
@override
void dispose() {
IsolateNameServer.removePortNameMapping(_isolateName);
BackgroundLocator.unRegisterLocationUpdate();
super.dispose();
}
accuracy
: Accuracy of location, default : LocationAccuracy.NAVIGATION
LocationAccuracy.NAVIGATION
LocationAccuracy.HIGH
LocationAccuracy.BALANCED
LocationAccuracy.POWERSAVE
LocationAccuracy.LOW
interval
: Interval of request the service make in second only for Android, default : 5
distanceFilter
: Distance in meter to trigger the callback, Default : 0
notificationTitle
: Title of the notification only for Android, default : 'Start Location Tracking'
notificationMsg
: Message of the notification, only for Android default : 'Track location in background'
notificationIcon
: Image of the notification, only for Android, the icon should be in 'mipmap' directory. Default : App icon
wakeLockTime
: Timeout in minutes for the service, only for Android, default : 60
autoStop
: If true the service will stop as soon as app goes to background