Flutter Foreground Service Location Tracking: A 2026 Deep‑Dive for Android and iOS
Implement reliable Flutter location tracking with Android foreground services and iOS background modes—permissions, code, and store compliance (Oct 2026).
Image used for representation purposes only.
Overview
Continuous, reliable location tracking in production Flutter apps almost always means running a foreground service on Android and enabling background location updates on iOS. This guide, updated for October 14, 2026, walks you through the architecture, permissions, code, and store-compliance steps to build a robust solution that survives app switches, screen locks, and process kills—without surprising your users or store reviewers. (developer.android.com )
Why a foreground service? On Android, it’s the supported way to keep long-running, user-perceptible work alive with a persistent notification; for continuous location, the service type must be location. On iOS, there’s no “service,” but you must enable background modes and request the correct authorization so Core Location can deliver updates when your app isn’t active. (developer.android.com )
Recommended stack
- geolocator: cross‑platform location stream (uses FusedLocationProvider on Android and CLLocationManager on iOS).
- flutter_foreground_task (Android): stable wrapper to run Dart in a dedicated foreground-service isolate with a persistent notification and two‑way messaging back to the UI. (pub.dev )
Alternatives exist (for example, other foreground-service wrappers or paid SDKs), but the pairing above is widely adopted and maintained. (pub.dev )
Android: foreground service location tracking
1) Manifest and Gradle setup
Declare runtime location permissions, background access (if you need tracking when the app isn’t visible), the foreground-service baseline permission, the specific foreground-service location permission on Android 14+, and a service with the correct type.
<!-- AndroidManifest.xml -->
<manifest ...>
<!-- Location runtime permissions -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Background location (Android 10+, request separately at runtime) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required for location-type FGS on Android 14+ (API 34+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application ...>
<!-- Foreground service declaration (flutter_foreground_task) -->
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:exported="false"
android:foregroundServiceType="location" />
</application>
</manifest>
Notes
- From Android 14+, starting a location-type foreground service without declaring FOREGROUND_SERVICE_LOCATION results in SecurityException. (learn.microsoft.com )
- You must set android:foregroundServiceType=“location” on the service; otherwise the system will throw a MissingForegroundServiceTypeException on newer Android versions. (developer.android.com )
2) Runtime permissions and start rules
- Ask for coarse/fine first, then background in a separate step only if truly needed. Android 12+ also lets users downgrade you to approximate location even if you ask for precise—design for that. (developers.google.com )
- Android 12+ blocks starting a foreground service from the background except for narrow exemptions; Android 14 tightens checks when the FGS needs “while‑in‑use” permissions (camera/mic/location). Start your service from a visible UI interaction, or ensure you qualify for an exemption. (developer.android.google.cn )
3) Foreground notification
Your FGS must display a persistent notification while running. Create a channel, set at least LOW priority, and keep text accurate (e.g., current speed or “Tracking active”). (developer.android.com )
4) Dart: wiring geolocator to a foreground-service isolate
The flutter_foreground_task plugin runs a TaskHandler in a background isolate. Subscribe to the location stream there and update the notification as fixes arrive.
// pubspec.yaml
// dependencies:
// geolocator: ^12.0.0
// flutter_foreground_task: ^11.0.3
// main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:geolocator/geolocator.dart';
@pragma('vm:entry-point')
class LocationTask extends TaskHandler {
StreamSubscription<Position>? _sub;
@override
Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
// Ensure permissions were granted before starting this service.
final settings = const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 25, // meters – tune for battery vs. fidelity
);
_sub = Geolocator.getPositionStream(locationSettings: settings).listen((pos) {
final text = 'Lat:${pos.latitude.toStringAsFixed(5)} '
'Lng:${pos.longitude.toStringAsFixed(5)} '
'Spd:${(pos.speed * 3.6).toStringAsFixed(1)}km/h';
FlutterForegroundTask.updateService(notificationText: text);
FlutterForegroundTask.sendDataToMain(jsonEncode({
'lat': pos.latitude,
'lng': pos.longitude,
'ts': pos.timestamp?.toIso8601String(),
'acc': pos.accuracy
}));
});
}
@override
Future<void> onDestroy(DateTime timestamp, bool isTimeout) async {
await _sub?.cancel();
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
FlutterForegroundTask.initCommunicationPort();
runApp(const App());
}
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
StreamSubscription? _taskDataSub;
@override
void initState() {
super.initState();
FlutterForegroundTask.addTaskDataCallback(_onTaskData);
}
void _onTaskData(dynamic data) {
// Handle JSON payload with new location; persist or send to server.
}
Future<void> _requestPermissions() async {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
await Geolocator.openLocationSettings();
return;
}
var status = await Geolocator.checkPermission();
if (status == LocationPermission.denied) {
status = await Geolocator.requestPermission(); // prompts coarse/fine
}
if (status == LocationPermission.whileInUse) {
// Request background only if your UX needs it.
status = await Geolocator.requestPermission(); // triggers BG prompt on Android 10+
}
}
Future<void> _startTracking() async {
await _requestPermissions();
await FlutterForegroundTask.startService(
notificationTitle: 'Tracking location',
notificationText: 'Starting…',
callback: (timestamp) => LocationTask(),
);
}
Future<void> _stopTracking() async {
await FlutterForegroundTask.stopService();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('FGS Location Tracker')),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _startTracking, child: const Text('Start')),
const SizedBox(width: 16),
ElevatedButton(onPressed: _stopTracking, child: const Text('Stop')),
],
),
),
),
);
}
}
The geolocator plugin handles the platform specifics (FLP on Android) and supports both precise and approximate flows. The foreground task plugin exposes a clean pattern for notification updates and main‑isolate messaging. (pub.dev )
5) Background vs foreground access
- To receive updates while your app isn’t visible, either hold ACCESS_BACKGROUND_LOCATION or run a location‑type foreground service. Many teams do both so the app can continue tracking during temporary service restarts. (developers.google.com )
iOS: background updates without a “service”
On iOS, enable the Background Modes capability for “Location updates,” request the right authorization strings, and (optionally) show Apple’s background-location indicator for transparency.
Info.plist essentials
- NSLocationWhenInUseUsageDescription
- NSLocationAlwaysAndWhenInUseUsageDescription (if you truly need “Always”)
- UIBackgroundModes = [ location ]
- Consider enabling showsBackgroundLocationIndicator at runtime to display the blue location indicator when tracking in the background. (developer.apple.com )
The geolocator plugin documents these requirements and how to avoid requesting “Always” if you don’t need it. If you do need background updates, enable the capability and include the Always key. (pub.dev )
Behavior note: When you start standard updates in the foreground and your app has background location capability, Core Location can continue delivering updates in the background; Always authorization may be required depending on your use case and iOS version. (developer.apple.com )
Accuracy, battery, and sampling strategy
- Prefer medium/balanced accuracy for most live-tracking; jump to high accuracy only when the UI is visible (e.g., active navigation). Tune distanceFilter/interval to reduce wakeups.
- Batch uploads on a timer or on distance thresholds; do not POST every fix.
- Consider significant-change monitoring or “visits” on iOS for passive scenarios.
- Show a clear in‑app switch to start/stop tracking; pause when stationary.
Store policy and disclosure (2026)
- Google Play: If your app accesses location in the background (including via an FGS used equivalently to background access), you must complete the Permissions Declaration Form, show a prominent in‑app disclosure before the runtime prompt, and ensure the feature is core to your app. Foreground access is preferred; use the minimum scope and consider the location button pattern. (support.google.com )
- Apple: Use accurate purpose strings, request “Always” only if necessary, and expect the background indicator to appear when tracking in the background. Apps that inappropriately request Always are likely to be rejected. (developer.apple.com )
End‑to‑end checklist
- Android
- Manifest: ACCESS_COARSE/FINE, ACCESS_BACKGROUND_LOCATION (if needed), FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION (API 34+), service with android:foregroundServiceType=“location”. (developer.android.google.cn )
- Request runtime perms in two steps (while‑in‑use → background). Start FGS from a visible UI to avoid background-start restrictions and while‑in‑use permission checks on Android 14+. (developer.android.google.cn )
- Persistent notification with channel; text reflects active tracking. (developer.android.com )
- iOS
- Capabilities: Background Modes → Location updates.
- Info.plist: NSLocationWhenInUseUsageDescription; NSLocationAlwaysAndWhenInUseUsageDescription (if needed); UIBackgroundModes=location. (developer.apple.com )
- Consider showsBackgroundLocationIndicator = true for transparency. (developer.apple.com )
- Flutter
- Use geolocator for streams; run collection code in flutter_foreground_task on Android. (pub.dev )
- Serialize and persist fixes locally; upload reliably when connectivity returns.
Troubleshooting tips
- SecurityException when starting FGS (Android 14+): confirm FOREGROUND_SERVICE_LOCATION is declared and that you start the service while your app is eligible (visible or exempt). (developer.android.google.cn )
- Service won’t start on Android 12+ when app is backgrounded: this is expected—start from user interaction or use an allowed exemption such as a geofence PendingIntent. (developer.android.google.cn )
- iOS doesn’t keep updating in background: verify Background Modes → Location updates is enabled, Info.plist strings exist, and your authorization is Always if your use case requires it. (developer.apple.com )
Final thoughts
Foreground-service location tracking in Flutter is straightforward once you align with modern platform rules: declare the right service type and permissions on Android, start from a user action, keep a clear notification; on iOS, enable background modes and request the right authorization. Build clear in‑app controls, collect the minimum necessary data, and disclose prominently—your users and reviewers will thank you. (developer.android.com )
Related Posts
Flutter Geolocation Tracking App Tutorial: Maps, Routes, and Background Updates
Build a production-ready Flutter geolocation tracker with maps, live routes, distance, and background updates. Permissions, battery, and privacy included.
Flutter Background Geofencing: A Practical, Production‑Ready Tutorial
Build reliable background geofencing in Flutter for Android and iOS: permissions, setup, code, testing, and production tips.
Flutter + Google Maps: A Complete Integration Guide
Integrate Google Maps in Flutter: setup keys, Android/iOS config, markers, directions, clustering, styling, and best practices.