flutter_bg_location 1.0.0 copy "flutter_bg_location: ^1.0.0" to clipboard
flutter_bg_location: ^1.0.0 copied to clipboard

A Flutter plugin for background location tracking that continues even when the app is killed. Features include persistent storage, real-time updates, and server sync.

Flutter Background Location Plugin #

A Flutter plugin for tracking device location in the background, even when the app is killed. Works on both Android and iOS with persistent location storage and optional server sync.

pub package Platform

Features #

Background Location Tracking - Continues tracking even when app is killed
Persistent Storage - Stores up to 1000 locations locally
Real-time Updates - EventChannel streaming when app is active
Server Sync - Optional HTTP POST to your backend
Auto-restart - Service restarts after device reboot
Battery Optimized - Configurable update intervals
Android Foreground Service - Persistent notification keeps service alive
iOS Significant Location Changes - Battery-efficient background tracking

Platform Support #

Platform Minimum Version
Android API 21 (5.0)
iOS 12.0

Installation #

Add this to your pubspec.yaml:

dependencies:
  flutter_bg_location: ^0.0.1
  permission_handler: ^11.0.0  # For permission management

Run:

flutter pub get

Platform Configuration #

Android Setup #

1. Update AndroidManifest.xml

Add to android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    <!-- Permissions -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.INTERNET" />
    
    <application>
        <!-- Your existing application content -->
    </application>
</manifest>

2. Gradle Configuration

Ensure minimum SDK in android/app/build.gradle:

android {
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 34
    }
}

iOS Setup #

1. Update Info.plist

Add to ios/Runner/Info.plist:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to your location in the background to track your movements even when the app is closed.</string>

<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location in the background to track your movements even when the app is closed.</string>

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location to track your movements.</string>

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
    <string>fetch</string>
</array>

2. Enable Background Modes in Xcode

  1. Open ios/Runner.xcworkspace in Xcode
  2. Select Runner target
  3. Go to Signing & Capabilities
  4. Click + CapabilityBackground Modes
  5. Check: Location updates

Usage #

Basic Example #

import 'package:flutter/material.dart';
import 'package:flutter_bg_location/flutter_bg_location.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Location? _currentLocation;
  bool _isTracking = false;

  @override
  void initState() {
    super.initState();
    _setupLocationTracking();
  }

  Future<void> _setupLocationTracking() async {
    // Configure notification (Android only)
    await FlutterBgLocation.setAndroidNotification(
      title: 'Location Tracking',
      message: 'Tracking your location in background',
      icon: '@mipmap/ic_launcher',
    );

    // Set update interval (Android only, in milliseconds)
    await FlutterBgLocation.setAndroidConfiguration(5000);

    // Listen to real-time location updates
    FlutterBgLocation.getLocationUpdates((location) {
      setState(() {
        _currentLocation = location;
      });
      print('Location: ${location.latitude}, ${location.longitude}');
    });
  }

  Future<void> _requestPermissions() async {
    // Request location permission
    await Permission.location.request();
    
    // Request background location (Always)
    await Permission.locationAlways.request();
    
    // Android: Request battery optimization exemption
    await Permission.ignoreBatteryOptimizations.request();
  }

  Future<void> _startTracking() async {
    await _requestPermissions();
    
    bool started = await FlutterBgLocation.startLocationService(
      distanceFilter: 10.0, // Update every 10 meters
    );
    
    setState(() {
      _isTracking = started;
    });
  }

  Future<void> _stopTracking() async {
    await FlutterBgLocation.stopLocationService();
    setState(() {
      _isTracking = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Background Location')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(_currentLocation?.toString() ?? 'No location'),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _isTracking ? _stopTracking : _startTracking,
                child: Text(_isTracking ? 'Stop' : 'Start'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

API Reference #

FlutterBgLocation #

Main class for interacting with the plugin.

Methods

setAndroidNotification

Configure the foreground service notification (Android only).

static Future<void> setAndroidNotification({
  required String title,
  required String message,
  String icon = '@mipmap/ic_launcher',
})

Parameters:

  • title (String, required): Notification title
  • message (String, required): Notification message
  • icon (String, optional): Icon resource name (default: @mipmap/ic_launcher)

Example:

await FlutterBgLocation.setAndroidNotification(
  title: 'GPS Tracking',
  message: 'Recording your route',
  icon: '@mipmap/ic_notification',
);

setAndroidConfiguration

Set the location update interval (Android only).

static Future<void> setAndroidConfiguration(int interval)

Parameters:

  • interval (int): Update interval in milliseconds

Example:

await FlutterBgLocation.setAndroidConfiguration(10000); // 10 seconds

startLocationService

Start the background location tracking service.

static Future<bool> startLocationService({
  double distanceFilter = 0,
  bool forceAndroidLocationManager = false,
})

Parameters:

  • distanceFilter (double, optional): Minimum distance (in meters) before update (default: 0)
  • forceAndroidLocationManager (bool, optional): Force use of LocationManager instead of FusedLocationProvider (default: false)

Returns:

  • Future<bool>: true if service started successfully

Example:

bool started = await FlutterBgLocation.startLocationService(
  distanceFilter: 50.0, // Only update every 50 meters
);

stopLocationService

Stop the background location tracking service.

static Future<void> stopLocationService()

Example:

await FlutterBgLocation.stopLocationService();

isServiceRunning

Check if the location tracking service is currently running.

static Future<bool> isServiceRunning()

Returns:

  • Future<bool>: true if service is running

Example:

bool isRunning = await FlutterBgLocation.isServiceRunning();
print('Service status: $isRunning');

getLocationUpdates

Listen to real-time location updates (only works when app is active).

static void getLocationUpdates(Function(Location) callback)

Parameters:

  • callback (Function): Callback function receiving Location objects

Example:

FlutterBgLocation.getLocationUpdates((location) {
  print('Lat: ${location.latitude}, Lng: ${location.longitude}');
  print('Accuracy: ${location.accuracy}m');
  print('Speed: ${location.speed}m/s');
});

getStoredLocations

Retrieve locations that were collected while the app was inactive.

static Future<List<Location>> getStoredLocations()

Returns:

  • Future<List<Location>>: List of stored Location objects (max 1000)

Example:

List<Location> locations = await FlutterBgLocation.getStoredLocations();
print('Found ${locations.length} stored locations');

for (var loc in locations) {
  print('${loc.latitude}, ${loc.longitude} at ${DateTime.fromMillisecondsSinceEpoch(loc.time.toInt())}');
}

clearStoredLocations

Clear all stored locations from local storage.

static Future<void> clearStoredLocations()

Example:

await FlutterBgLocation.clearStoredLocations();
print('Storage cleared');

setCallbackUrl

Configure automatic HTTP POST of locations to your server.

static Future<void> setCallbackUrl(
  String url, {
  Map<String, String>? headers,
})

Parameters:

  • url (String): Your server endpoint URL
  • headers (Map<String, String>, optional): Custom HTTP headers

Example:

await FlutterBgLocation.setCallbackUrl(
  'https://api.example.com/locations',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json',
  },
);

Server Request Format:

{
  "latitude": 37.7749,
  "longitude": -122.4194,
  "altitude": 10.5,
  "accuracy": 5.0,
  "bearing": 45.0,
  "speed": 2.5,
  "time": 1704326400000,
  "is_mock": false
}

Location Class #

Represents a geographic location.

Properties

class Location {
  final double latitude;      // Latitude in degrees
  final double longitude;     // Longitude in degrees
  final double altitude;      // Altitude in meters above sea level
  final double accuracy;      // Accuracy in meters
  final double bearing;       // Direction of travel in degrees (0-360)
  final double speed;         // Speed in meters per second
  final double time;          // Timestamp in milliseconds since epoch
  final bool isMock;         // Whether this is a mock location (Android)
}

Methods

// Convert to Map
Map<String, dynamic> toMap()

// Create from Map
factory Location.fromMap(Map<dynamic, dynamic> map)

// String representation
String toString()

Example:

Location loc = location;
print('Coordinates: ${loc.latitude}, ${loc.longitude}');
print('Accuracy: ${loc.accuracy}m');
print('Speed: ${loc.speed * 3.6} km/h');  // Convert m/s to km/h
print('Time: ${DateTime.fromMillisecondsSinceEpoch(loc.time.toInt())}');

Permission Handling #

Android Permissions #

Future<void> requestAndroidPermissions() async {
  // Step 1: Request fine location
  var locationStatus = await Permission.location.request();
  
  if (locationStatus.isGranted) {
    // Step 2: Request background location (Android 10+)
    await Permission.locationAlways.request();
    
    // Step 3: Request battery optimization exemption
    await Permission.ignoreBatteryOptimizations.request();
  }
}

Android 10+ (API 29+): Background location must be requested separately after foreground location.

iOS Permissions #

Future<void> requestIOSPermissions() async {
  // Step 1: Request "When In Use" permission
  var status = await Permission.locationWhenInUse.request();
  
  if (status.isGranted) {
    // Step 2: Request "Always" permission
    await Permission.locationAlways.request();
  }
}

iOS: User must explicitly grant "Always" permission for background tracking.

Advanced Usage #

Complete Example with All Features #

import 'package:flutter/material.dart';
import 'package:flutter_bg_location/flutter_bg_location.dart';
import 'package:permission_handler/permission_handler.dart';

class LocationTracker extends StatefulWidget {
  @override
  _LocationTrackerState createState() => _LocationTrackerState();
}

class _LocationTrackerState extends State<LocationTracker> with WidgetsBindingObserver {
  Location? _currentLocation;
  bool _isTracking = false;
  List<Location> _storedLocations = [];

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initialize();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // App came back to foreground, reload stored locations
      _loadStoredLocations();
    }
  }

  Future<void> _initialize() async {
    // Configure notification
    await FlutterBgLocation.setAndroidNotification(
      title: 'Location Tracking Active',
      message: 'We are tracking your location',
      icon: '@mipmap/ic_launcher',
    );

    // Configure update interval
    await FlutterBgLocation.setAndroidConfiguration(5000);

    // Setup server sync (optional)
    await FlutterBgLocation.setCallbackUrl(
      'https://your-api.com/locations',
      headers: {'Authorization': 'Bearer YOUR_TOKEN'},
    );

    // Listen to updates
    FlutterBgLocation.getLocationUpdates((location) {
      setState(() {
        _currentLocation = location;
      });
    });

    // Check if already running
    bool isRunning = await FlutterBgLocation.isServiceRunning();
    setState(() {
      _isTracking = isRunning;
    });

    // Load stored locations
    await _loadStoredLocations();
  }

  Future<void> _loadStoredLocations() async {
    List<Location> locations = await FlutterBgLocation.getStoredLocations();
    setState(() {
      _storedLocations = locations;
    });
  }

  Future<void> _requestPermissions() async {
    await Permission.location.request();
    await Permission.locationAlways.request();
    await Permission.ignoreBatteryOptimizations.request();
  }

  Future<void> _startTracking() async {
    await _requestPermissions();
    
    bool started = await FlutterBgLocation.startLocationService(
      distanceFilter: 10.0,
    );
    
    if (started) {
      setState(() {
        _isTracking = true;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Tracking started')),
      );
    }
  }

  Future<void> _stopTracking() async {
    await FlutterBgLocation.stopLocationService();
    setState(() {
      _isTracking = false;
    });
  }

  Future<void> _clearLocations() async {
    await FlutterBgLocation.clearStoredLocations();
    await _loadStoredLocations();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Location Tracker'),
        backgroundColor: _isTracking ? Colors.green : Colors.grey,
      ),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Status
            Card(
              child: ListTile(
                leading: Icon(
                  _isTracking ? Icons.check_circle : Icons.cancel,
                  color: _isTracking ? Colors.green : Colors.grey,
                ),
                title: Text(_isTracking ? 'Tracking Active' : 'Tracking Stopped'),
                subtitle: Text('Works even when app is killed'),
              ),
            ),
            
            SizedBox(height: 16),
            
            // Current Location
            Text('Current Location:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            Card(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: _currentLocation != null
                    ? Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text('Lat: ${_currentLocation!.latitude.toStringAsFixed(6)}'),
                          Text('Lng: ${_currentLocation!.longitude.toStringAsFixed(6)}'),
                          Text('Accuracy: ${_currentLocation!.accuracy.toStringAsFixed(2)}m'),
                          Text('Speed: ${(_currentLocation!.speed * 3.6).toStringAsFixed(2)} km/h'),
                        ],
                      )
                    : Text('Waiting for location...'),
              ),
            ),
            
            SizedBox(height: 16),
            
            // Stored Locations
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('Stored: ${_storedLocations.length}', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                if (_storedLocations.isNotEmpty)
                  TextButton(
                    onPressed: _clearLocations,
                    child: Text('Clear'),
                  ),
              ],
            ),
            
            Expanded(
              child: Card(
                child: _storedLocations.isEmpty
                    ? Center(child: Text('No stored locations'))
                    : ListView.builder(
                        itemCount: _storedLocations.length,
                        itemBuilder: (context, index) {
                          final loc = _storedLocations[index];
                          final time = DateTime.fromMillisecondsSinceEpoch(loc.time.toInt());
                          return ListTile(
                            leading: Icon(Icons.location_on),
                            title: Text('${loc.latitude.toStringAsFixed(4)}, ${loc.longitude.toStringAsFixed(4)}'),
                            subtitle: Text('${time.hour}:${time.minute}:${time.second}'),
                          );
                        },
                      ),
              ),
            ),
            
            SizedBox(height: 16),
            
            // Controls
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isTracking ? null : _startTracking,
                    child: Text('Start'),
                    style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                  ),
                ),
                SizedBox(width: 16),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isTracking ? _stopTracking : null,
                    child: Text('Stop'),
                    style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                  ),
                ),
              ],
            ),
            
            SizedBox(height: 8),
            
            SizedBox(
              width: double.infinity,
              child: OutlinedButton(
                onPressed: _loadStoredLocations,
                child: Text('Reload Stored Locations'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Platform Behaviors #

Android #

Feature Behavior
Tracking Method FusedLocationProvider (Google Play Services)
Update Frequency Configurable (default: 5 seconds)
Foreground Service Yes (persistent notification required)
Survives App Kill Yes (if battery optimization disabled)
Auto-restart Yes (on device reboot)
Battery Impact Moderate (depends on update interval)

iOS #

Feature Behavior
Tracking Method CoreLocation (CLLocationManager)
Update Frequency System-managed
Background Indicator Blue status bar shown
Survives App Kill Yes (significant location changes only)
Update Distance ~500 meters (significant changes)
Battery Impact Low (iOS optimized)

Troubleshooting #

Android Issues #

Service stops when app is killed:

// Ensure battery optimization is disabled
await Permission.ignoreBatteryOptimizations.request();

No location updates:

  • Check GPS is enabled on device
  • Verify permissions granted (including background location)
  • Test outdoors for better GPS signal

Notification doesn't appear:

  • Verify setAndroidNotification() is called before startLocationService()
  • Check notification permissions are granted

iOS Issues #

No background updates:

  • Verify "Always" permission is granted
  • Check Background Modes are enabled in Xcode
  • Ensure Info.plist has all required keys

Blue bar disappears:

// Verify in FlutterBgLocationPlugin.swift:
locationManager?.allowsBackgroundLocationUpdates = true
locationManager?.pausesLocationUpdatesAutomatically = false

App not waking up:

  • iOS uses significant location changes (500m threshold)
  • Move more than 500 meters to trigger update

Best Practices #

Battery Optimization #

// Use larger distance filter
await FlutterBgLocation.startLocationService(
  distanceFilter: 100.0, // Only update every 100 meters
);

// Increase update interval (Android)
await FlutterBgLocation.setAndroidConfiguration(30000); // 30 seconds

Data Management #

// Periodically sync to server and clear local storage
Future<void> syncLocations() async {
  List<Location> locations = await FlutterBgLocation.getStoredLocations();
  
  // Send to your server
  await sendToServer(locations);
  
  // Clear after successful sync
  await FlutterBgLocation.clearStoredLocations();
}

User Experience #

// Always explain why you need location permission
Future<void> requestWithExplanation(BuildContext context) async {
  await showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('Location Permission'),
      content: Text('We need your location to [explain your use case]'),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            Permission.location.request();
          },
          child: Text('OK'),
        ),
      ],
    ),
  );
}

Testing #

Test on Physical Devices #

  • ✅ Android: Use real device, not emulator
  • ✅ iOS: Use real device (simulators have limited GPS)

Test Scenarios #

  1. Foreground tracking: App open, verify real-time updates
  2. Background tracking: App minimized, verify tracking continues
  3. Killed app tracking: Kill app completely, move around, reopen and check stored locations
  4. Reboot test: Restart device with tracking enabled, verify auto-restart
  5. Battery test: Monitor battery usage over extended period

Performance Metrics #

Metric Typical Value
Update latency < 1 second
Memory usage 10-30 MB
Battery drain (per hour) 2-5%
Storage per location ~200 bytes
Max stored locations 1000

FAQ #

Q: Does this work when the app is completely killed?
A: Yes! Locations are stored locally and can be retrieved when the app reopens.

Q: What's the difference between Android and iOS tracking?
A: Android provides continuous updates; iOS uses significant location changes (every ~500m) when app is killed.

Q: How much battery does this use?
A: Varies by update frequency. Typical usage: 2-5% per hour. Use larger distance filters to reduce impact.

Q: Can I send locations to my server?
A: Yes! Use setCallbackUrl() to automatically POST locations to your backend.

Q: What happens if I reach 1000 stored locations?
A: Oldest locations are automatically removed to make room for new ones (FIFO).

Q: Do I need Google Play Services on Android?
A: By default yes, but you can use forceAndroidLocationManager: true to use the system LocationManager instead.

License #

MIT License - See LICENSE file for details

Contributing #

Contributions are welcome! Please submit pull requests to the GitHub repository.

Support #

Changelog #

0.0.1 #

  • Initial release
  • Android and iOS support
  • Background location tracking
  • Local storage up to 1000 locations
  • Server sync capability
  • Auto-restart on reboot

Made with ❤️ for the Flutter community

5
likes
160
points
0
downloads
screenshot

Publisher

verified publisherashishcodes.site

Weekly Downloads

A Flutter plugin for background location tracking that continues even when the app is killed. Features include persistent storage, real-time updates, and server sync.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_bg_location

Packages that implement flutter_bg_location