I am creating a Flutter app that requires fetching the location of the user when the app is closed with a frequency of around 15-30 seconds. Currently, I use the ‘location’ flutter package which allows me to fetch the location when the app is open but not on screen. However, I want the location to be tracked when I close the app, and also turn on the phone.
I have tried using geolocator and workmanager to retrieve the location, but those have minimum frequencies of 15 minutes, which is far too infrequent. I currently use the ‘location’ package, however it stops running when the app is closed and does not run on startup. I have found flutter_background_geolocation, however it is not open source.
This is my implementation:
myapp/android/app/main/java/com/flutter/myapp/LocationService.kt
package com.flutter.myapp
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.FusedLocationProviderClient
class LocationService : Service() {
private val CHANNEL_ID = "ForegroundServiceChannel"
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
override fun onCreate() {
super.onCreate()
createNotificationChannel()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
locationResult?.let {
// Handle location update here
val location = it.lastLocation
}
}
startLocationUpdates()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
private fun startLocationUpdates() {
val locationRequest = LocationRequest.create().apply {
interval = 15000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Location Service")
.setContentText("Tracking your location...")
.setContentIntent(getPendingIntent())
.build()
startForeground(1, notification)
return START_STICKY
}
private fun getPendingIntent(): PendingIntent {
val notificationIntent = Intent(this, MainActivity::class.java)
return PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
}
Addition to myapp/android/app/main/kotlin/com/flutter/myapp/MainActivity.kt:
<service
android:name=".LocationService"
android:enabled="true"
android:exported="false"/>
Usage of service:
class ForegroundService {
static const platform =
MethodChannel('com.flutter.myapp/locationService');
Future<void> startService() async {
try {
final intent = AndroidIntent(
action: 'com.flutter.myapp.START_LOCATION_SERVICE',
package: 'com.flutter.myapp',
componentName: 'com.flutter.myapp/.LocationService',
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
);
intent.sendBroadcast();
await platform.invokeMethod('startService');
} on PlatformException catch (e) {
print("Failed to start service: '${e.message}'.");
}
}
Future<void> stopService() async {
try {
await platform.invokeMethod('stopService');
} on PlatformException catch (e) {
print("Failed to stop service: '${e.message}'.");
}
}
When I close the app (i.e. swiping up in the list of apps), this is printed in the logs:
D/FlutterGeolocator(27222): Detaching Geolocator from activity
D/FlutterGeolocator(27222): Flutter engine disconnected. Connected engine count 0
D/FlutterGeolocator(27222): Disposing Geolocator services
E/FlutterGeolocator(27222): Geolocator position updates stopped
D/FlutterGeolocator(27222): Stopping location service.
D/FlutterLocationService(27222): Unbinding from location service.
D/FlutterLocationService(27222): Destroying service.
D/FlutterGeolocator(27222): Unbinding from location service.
D/FlutterGeolocator(27222): Destroying location service.
D/FlutterGeolocator(27222): Stopping location service.
D/FlutterGeolocator(27222): Destroyed location service.
2
Answers
There is the package background locator that might do the trick.
Please, check this issue, hope it helps.
flutter_background_service helped in my case.
There documentation explains most of the things