location_tracking 2.0.8
location_tracking: ^2.0.8 copied to clipboard
A Flutter plugin for receiving location updates even when the app is terminated. Works on both iOS and Android with support for background execution.
example/lib/main.dart
import 'dart:async';
import 'dart:isolate';
import 'dart:ui';
import 'package:location_tracking/background_locator.dart';
import 'package:location_tracking/location_dto.dart';
import 'package:location_tracking/settings/android_settings.dart';
import 'package:location_tracking/settings/ios_settings.dart';
import 'package:location_tracking/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart' as permission_handler;
import 'file_manager.dart';
import 'location_callback_handler.dart';
import 'location_service_repository.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ReceivePort port = ReceivePort();
String logStr = '';
bool? isRunning;
LocationDto? lastLocation;
bool isInsideGeofence = false;
@override
void initState() {
super.initState();
if (IsolateNameServer.lookupPortByName(
LocationServiceRepository.isolateName) !=
null) {
IsolateNameServer.removePortNameMapping(
LocationServiceRepository.isolateName);
}
IsolateNameServer.registerPortWithName(
port.sendPort, LocationServiceRepository.isolateName);
port.listen(
(dynamic data) async {
await updateUI(data);
},
);
initPlatformState();
}
@override
void dispose() {
super.dispose();
IsolateNameServer.removePortNameMapping(
LocationServiceRepository.isolateName);
}
Future<void> updateUI(dynamic data) async {
final log = await FileManager.readLogFile();
LocationDto? locationDto;
bool? geofenceStatus;
if (data != null && data is Map<String, dynamic>) {
locationDto = LocationDto.fromJson(data['location']);
geofenceStatus = data['isInsideGeofence'];
}
if (locationDto != null) {
await _updateNotificationText(locationDto, geofenceStatus);
}
setState(() {
if (locationDto != null) {
lastLocation = locationDto;
}
if (geofenceStatus != null) {
isInsideGeofence = geofenceStatus;
}
logStr = log;
});
}
Future<void> _updateNotificationText(
LocationDto data, bool? insideGeofence) async {
String geofenceStatus =
insideGeofence == true ? "Inside geofence" : "Outside geofence";
await BackgroundLocator.updateNotificationText(
title: "New location received",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}\n$geofenceStatus");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
print('Initialization done');
final isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = isRunning;
});
print('Running ${isRunning.toString()}');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
scaffoldBackgroundColor: const Color(0xFF1E1E1E),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Geofence Tracker'),
elevation: 0,
centerTitle: true,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_buildStatusCard(),
const SizedBox(height: 16),
_buildControlButtons(),
const SizedBox(height: 16),
Expanded(child: _buildLogCard()),
],
),
),
),
),
);
}
Widget _buildStatusCard() {
return Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.blueAccent, Colors.lightBlue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
isRunning == true ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 8),
Text(
"Geofence: ${isInsideGeofence ? 'Inside' : 'Outside'}",
style: const TextStyle(fontSize: 16, color: Colors.white70),
),
],
),
),
),
);
}
Widget _buildControlButtons() {
return Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
onPressed: _onStart,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
backgroundColor: Colors.green,
shadowColor: Colors.greenAccent,
elevation: 5,
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.stop),
label: const Text('Stop'),
onPressed: onStop,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
shadowColor: Colors.redAccent,
elevation: 5,
),
),
),
],
);
}
Widget _buildLogCard() {
return Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Log',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
FileManager.clearLogFile();
setState(() {
logStr = '';
});
},
),
],
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Text(logStr),
),
),
],
),
);
}
void onStop() async {
await BackgroundLocator.unRegisterLocationUpdate();
final isRunning = await BackgroundLocator.isServiceRunning();
print('Locator stopped: $isRunning');
setState(() {
isRunning = isRunning;
});
}
void _onStart() async {
if (await _checkLocationPermission()) {
print('Permissions granted, starting locator...');
await _startLocator();
final isRunning = await BackgroundLocator.isServiceRunning();
print('Locator started: $isRunning');
setState(() {
isRunning = isRunning;
lastLocation = null;
});
print('Locator started: $isRunning');
} else {
print('Permissions not granted');
// show error
}
}
Future<bool> _checkLocationPermission() async {
final access = await permission_handler.Permission.location.status;
print('Current permission status: $access');
switch (access) {
case permission_handler.PermissionStatus.limited:
case permission_handler.PermissionStatus.denied:
case permission_handler.PermissionStatus.restricted:
final permission = await permission_handler.Permission.location.request();
return permission == permission_handler.PermissionStatus.granted;
case permission_handler.PermissionStatus.granted:
return true;
default:
return false;
}
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
LocationCallbackHandler.callback,
initCallback: LocationCallbackHandler.initCallback,
initDataCallback: data,
disposeCallback: LocationCallbackHandler.disposeCallback,
iosSettings: const IOSSettings(
accuracy: LocationAccuracy.NAVIGATION,
distanceFilter: 0,
stopWithTerminate: true),
autoStop: false,
androidSettings: const AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-to-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback:
LocationCallbackHandler.notificationCallback)));
}
}