flutter_bg_location 1.0.0
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.
example/lib/main.dart
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bg_location/flutter_bg_location.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Background Location Tracker',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const LocationTrackerPage(),
);
}
}
class LocationTrackerPage extends StatefulWidget {
const LocationTrackerPage({super.key});
@override
LocationTrackerPageState createState() => LocationTrackerPageState();
}
class LocationTrackerPageState extends State<LocationTrackerPage>
with WidgetsBindingObserver {
Location? _currentLocation;
bool _isTracking = false;
List<Location> _storedLocations = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_setupLocationTracking();
_checkServiceStatus();
_loadStoredLocations();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_loadStoredLocations();
}
}
Future<void> _setupLocationTracking() async {
try {
await FlutterBgLocation.setAndroidNotification(
title: 'Location Tracking Active',
message: 'Your location is being tracked in background',
icon: '@mipmap/ic_launcher',
);
await FlutterBgLocation.setAndroidConfiguration(5000);
FlutterBgLocation.getLocationUpdates((location) {
setState(() {
_currentLocation = location;
});
if (kDebugMode) {
print('Real-time location: $location');
}
});
} catch (e) {
if (kDebugMode) {
print('Error setting up location tracking: $e');
}
}
}
Future<void> _checkServiceStatus() async {
try {
final isRunning = await FlutterBgLocation.isServiceRunning();
setState(() {
_isTracking = isRunning;
});
} catch (e) {
if (kDebugMode) {
print('Error checking service status: $e');
}
}
}
Future<void> _loadStoredLocations() async {
try {
final locations = await FlutterBgLocation.getStoredLocations();
setState(() {
_storedLocations = locations;
});
if (kDebugMode) {
print('Loaded ${locations.length} stored locations');
}
} catch (e) {
if (kDebugMode) {
print('Error loading stored locations: $e');
}
}
}
Future<void> _requestPermissions() async {
// For iOS, we need to request in stages
if (Platform.isIOS) {
// First request "When In Use"
var status = await Permission.locationWhenInUse.request();
if (status.isGranted) {
// Then request "Always"
await Future.delayed(const Duration(seconds: 1));
if (mounted) {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Background Location'),
content: const Text(
'To track your location in the background, please select '
'"Allow While Using App" first, then we\'ll ask for '
'"Always" permission.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
await Permission.locationAlways.request();
}
} else {
// Android
var status = await Permission.location.request();
if (status.isGranted) {
if (await Permission.locationAlways.isDenied) {
if (mounted) {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Background Location Permission'),
content: const Text(
'To track location when app is closed, please allow '
'"Allow all the time" in the next permission dialog.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
await Permission.locationAlways.request();
}
await Permission.ignoreBatteryOptimizations.request();
}
}
}
Future<void> _startTracking() async {
await _requestPermissions();
try {
final success = await FlutterBgLocation.startLocationService(
distanceFilter: 10.0,
);
if (success) {
setState(() {
_isTracking = true;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Location tracking started')),
);
}
}
} catch (e) {
if (kDebugMode) {
print('Error starting tracking: $e');
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
}
Future<void> _stopTracking() async {
try {
await FlutterBgLocation.stopLocationService();
setState(() {
_isTracking = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Location tracking stopped')),
);
}
} catch (e) {
if (kDebugMode) {
print('Error stopping tracking: $e');
}
}
}
Future<void> _clearStoredLocations() async {
try {
await FlutterBgLocation.clearStoredLocations();
await _loadStoredLocations();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Stored locations cleared')),
);
}
} catch (e) {
if (kDebugMode) {
print('Error clearing locations: $e');
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Background Location Tracker'),
backgroundColor: _isTracking ? Colors.green : Colors.grey,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Service Status Card
Card(
color: _isTracking ? Colors.green[50] : Colors.grey[50],
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
_isTracking ? Icons.check_circle : Icons.cancel,
color: _isTracking ? Colors.green : Colors.grey,
),
const SizedBox(width: 8),
Text(
_isTracking ? 'Service Running' : 'Service Stopped',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
Text(
_isTracking
? 'Location is being tracked even when app is killed'
: 'Start tracking to collect location data',
style: TextStyle(color: Colors.grey[700]),
),
],
),
),
),
const SizedBox(height: 20),
// Current Location Card
const Text(
'Current Location (Real-time):',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: _currentLocation != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Latitude: ${_currentLocation!.latitude.toStringAsFixed(6)}'),
Text(
'Longitude: ${_currentLocation!.longitude.toStringAsFixed(6)}'),
Text(
'Accuracy: ${_currentLocation!.accuracy.toStringAsFixed(2)}m'),
Text(
'Speed: ${_currentLocation!.speed.toStringAsFixed(2)}m/s'),
],
)
: const Text('No location data yet'),
),
),
const SizedBox(height: 20),
// Stored Locations
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Stored Locations: ${_storedLocations.length}',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
if (_storedLocations.isNotEmpty)
TextButton.icon(
onPressed: _clearStoredLocations,
icon: const Icon(Icons.delete),
label: const Text('Clear'),
),
],
),
const SizedBox(height: 8),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: _storedLocations.isEmpty
? const Text('No stored locations yet')
: SizedBox(
height: 200,
child: ListView.builder(
itemCount: _storedLocations.length > 10
? 10
: _storedLocations.length,
itemBuilder: (context, index) {
final loc = _storedLocations[
_storedLocations.length - 1 - index];
return ListTile(
dense: true,
leading: const Icon(Icons.location_on, size: 16),
title: Text(
'${loc.latitude.toStringAsFixed(4)}, ${loc.longitude.toStringAsFixed(4)}',
),
);
},
),
),
),
),
const SizedBox(height: 20),
// Control Buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isTracking ? null : _startTracking,
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: _isTracking ? _stopTracking : null,
icon: const Icon(Icons.stop),
label: const Text('Stop'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
),
],
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _loadStoredLocations,
icon: const Icon(Icons.refresh),
label: const Text('Reload Stored Locations'),
),
),
],
),
),
);
}
}