fl_coffee_geolocation 1.0.1
fl_coffee_geolocation: ^1.0.1 copied to clipboard
The Coffee Flutter Geolocation plugin offers robust and efficient location tracking capabilities for Flutter applications, especially designed for background operation.
example/lib/main.dart
import 'package:fl_coffee_geolocation/models/config.dart';
import 'package:fl_coffee_geolocation/models/location.dart';
import 'package:fl_coffee_geolocation/models/location_config.dart';
import 'package:fl_coffee_geolocation/models/notification.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:fl_coffee_geolocation/fl_coffee_geolocation.dart';
import 'dart:math' as math;
import 'location_settings_dialog.dart';
const primaryColor = Color.fromRGBO(2,71,76, 1);
const accentColor = Color.fromRGBO(120,202,78, 1);
const yellowColor = Color.fromRGBO(200,229,31, 1);
void main() {
runApp(const GetMaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Coffee Background Geolocation Example',
home: CoffeeBackgroudGeolocationScreen(), // Set LocationMapScreen as the home screen
);
}
}
class CoffeeBackgroudGeolocationScreen extends StatefulWidget {
const CoffeeBackgroudGeolocationScreen({super.key});
@override
// ignore: library_private_types_in_public_api
_CoffeeBackgroudGeolocationScreenState createState() => _CoffeeBackgroudGeolocationScreenState();
}
class _CoffeeBackgroudGeolocationScreenState extends State<CoffeeBackgroudGeolocationScreen> {
bool _isLoading = true;
bool _isTracking = false;
bool _isInitializing = false;
bool _initSucceeded = false;
bool _isLocationCardExpanded = true;
CoffeeBackgroundLocation? _currentLocation;
final MapController _mapController = MapController();
List<Marker> _currentMarkers = [];
List<Marker> _updatedLocationMarkers = [];
List<Polygon> _geofenceMarkers = [];
List<CoffeeBackgroundLocation> _pathlinePositions = [];
double _distanceFilter = 0;
bool _showFences = true;
@override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 2),() {
_getCurrentLocation();
});
}
Future<void> _fetchAndSetDistanceFilter() async {
LocationConfig? locationSettings = await CoffeeBackgroundGeolocation.getLocationSettings();
if (locationSettings != null) {
setState(() {
_distanceFilter = locationSettings.distanceFilter ?? 0;
});
}
}
Future<void> _init() async {
setState(() {
_isInitializing = true;
});
try {
await CoffeeBackgroundGeolocation.init(Config(
location: LocationConfig(
desiredAccuracy: Config.desiredAcuracyNavigation,
distanceFilter: 5
),
debug: true,
notification: CoffeeBackgroundNotification(
priority: Config.notificationPriorityHigh,
title: "Running in background",
text: "This is a message for the notification"
)
));
await _fetchAndSetDistanceFilter();
setState(() {
_initSucceeded = true;
});
} catch (e) {
Get.snackbar(
"Initialization Error",
"Failed to initialize: $e",
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.redAccent,
colorText: Colors.white,
borderRadius: 10,
margin: const EdgeInsets.all(10),
duration: const Duration(seconds: 3),
);
}
setState(() {
_isInitializing = false;
});
}
void _getCurrentLocation() async {
setState(() {
_isLoading = true;
});
try {
CoffeeBackgroundLocation? location = await CoffeeBackgroundGeolocation.getCurrentLocation();
if (location == null) {
setState(() {
_isLoading = false;
});
Get.snackbar(
"Error",
"Failed to get current location",
snackPosition: SnackPosition.BOTTOM,
borderRadius: 10,
margin: const EdgeInsets.all(10),
duration: const Duration(seconds: 2),
);
return;
}
final currentMarker = _buildMarker(
latitude: location.latitude,
longitude: location.longitude,
heading: location.heading,
color: Colors.blue
);
if(_currentLocation == null) {
_centerMap(location, zoom: 15);
}
setState(() {
_currentLocation = location;
_isLoading = false;
_currentMarkers = [currentMarker];
});
} catch (e) {
setState(() {
_isLoading = false;
});
Get.snackbar(
"Error",
"Failed to get location: $e",
snackPosition: SnackPosition.BOTTOM,
borderRadius: 10,
margin: const EdgeInsets.all(10),
duration: const Duration(seconds: 2),
);
}
}
void _toggleTracking() async {
if(!_initSucceeded) {
Get.snackbar(
"Initialization Required",
"Please initialize the Coffee Background Geolocation service first by pressing the 'Initialize Coffee' button.",
snackPosition: SnackPosition.BOTTOM,
borderRadius: 10,
margin: const EdgeInsets.all(10),
duration: const Duration(seconds: 2),
);
return;
}
if (_isTracking) {
await CoffeeBackgroundGeolocation.stop();
} else {
await CoffeeBackgroundGeolocation.start();
CoffeeBackgroundGeolocation.onLocation((CoffeeBackgroundLocation location) {
if(_currentLocation == null) {
_centerMap(location, zoom: 15);
}
_handleLocationUpdate(location);
});
}
setState(() {
_isTracking = !_isTracking;
});
}
void _handleLocationUpdate(CoffeeBackgroundLocation location) {
// Add the new location to the pathline
_pathlinePositions.add(location);
// Add a new geofence marker
List<LatLng> circlePoints = createCirclePoints(
LatLng(location.latitude, location.longitude),
_distanceFilter
);
Polygon geofenceCircle = Polygon(
points: circlePoints,
color: accentColor.withOpacity(0.5),
borderColor: accentColor,
isFilled: true,
borderStrokeWidth: 2.0,
);
setState(() {
_geofenceMarkers.add(geofenceCircle);
});
// Update UI
final updateMarker = _buildMarker(
latitude: location.latitude,
longitude: location.longitude,
heading: location.heading,
color: primaryColor
);
setState(() {
_pathlinePositions = _pathlinePositions;
_geofenceMarkers = _geofenceMarkers;
_updatedLocationMarkers = [updateMarker];
_currentLocation = location;
});
}
Future<void> _openDialogSettings() async {
if(!_initSucceeded) {
Get.snackbar(
"Initialization Required",
"Please initialize the Coffee Background Geolocation service first by pressing the 'Initialize Coffee' button.",
snackPosition: SnackPosition.BOTTOM,
borderRadius: 10,
margin: const EdgeInsets.all(10),
duration: const Duration(seconds: 2),
);
return;
}
final result = await showDialog(
context: context,
builder: (BuildContext context) {
return LocationSettingsDialog(showFences: _showFences);
},
);
if(result != null) {
if(result['resetPathLine'] == true) {
setState(() {
_pathlinePositions = [];
_geofenceMarkers = [];
});
}
setState(() {
_showFences = result['showFences'];
});
_fetchAndSetDistanceFilter();
}
}
void _centerMap(CoffeeBackgroundLocation location, { double? zoom }) {
_mapController.move(LatLng(location.latitude, location.longitude), zoom ?? _mapController.camera.zoom);
}
List<LatLng> createCirclePoints(LatLng center, double radiusInMeters) {
const int circlePointsCount = 60; // Adjust for desired polygon density
List<LatLng> points = [];
for (int i = 0; i < circlePointsCount; i++) {
double angle = (360 / circlePointsCount) * i;
// Convert angle and radius to radians
double radians = angle * (math.pi / 180);
double earthRadius = 6378137.0; // Radius of the Earth in meters
double latRadian = center.latitude * (math.pi / 180);
double dx = radiusInMeters * math.cos(radians);
double dy = radiusInMeters * math.sin(radians);
double lat = center.latitude + (dy / earthRadius) * (180 / math.pi);
double lon = center.longitude + (dx / (earthRadius * math.cos(latRadian))) * (180 / math.pi);
points.add(LatLng(lat, lon));
}
return points;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Coffee Background Geolocation',
style: TextStyle(color: yellowColor),
),
backgroundColor: primaryColor,
),
body: SafeArea(
child: FlutterMap(
mapController: _mapController,
options: const MapOptions(
initialCenter: LatLng(0, 0), // Default center
initialZoom: 13.0,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: const ['a', 'b', 'c'],
),
_showFences ?
PolygonLayer(
polygons: _geofenceMarkers,
) : const SizedBox(),
PolylineLayer(polylines: [
Polyline(
points: _pathlinePositions.map((e) => LatLng(e.latitude, e.longitude)).toList(),
strokeWidth: 4.0,
color: Colors.green,
),
]),
MarkerLayer(
markers: _pathlinePositions.map((e) => _buildMarker(
latitude: e.latitude,
longitude: e.longitude,
heading: e.heading,
color: Colors.grey
)).toList(),
),
MarkerLayer(
markers: _isTracking ? _currentMarkers : _updatedLocationMarkers,
),
MarkerLayer(
markers: _isTracking ? _updatedLocationMarkers : _currentMarkers,
),
Stack(
children: [
Positioned(
left: 10,
bottom: 10,
child: _currentLocation != null ? _buildLocationCard() : const SizedBox(),
),
Positioned(
top: 20,
right: 10,
child: Column(
children: [
FloatingActionButton(
onPressed: _getCurrentLocation,
backgroundColor: _isLoading ? Colors.grey : primaryColor,
child: _isLoading ?
const CircularProgressIndicator(color: Colors.white) :
const Icon(Icons.my_location, color: Colors.white,),
),
const SizedBox(height: 20),
FloatingActionButton(
onPressed: _toggleTracking,
backgroundColor: Colors.white,
child: Icon(
_isTracking ? Icons.stop : Icons.play_arrow,
color: _isTracking ? Colors.red : accentColor,
),
),
const SizedBox(height: 20),
FloatingActionButton(
onPressed: _openDialogSettings,
backgroundColor: Colors.white,
child: const Icon(Icons.settings,
color: primaryColor,
),
),
const SizedBox(height: 20),
FloatingActionButton(
onPressed: () {
if(_currentLocation == null) {
return;
}
_centerMap(_currentLocation!);
},
backgroundColor: yellowColor,
child: const Icon(Icons.center_focus_strong,
color: primaryColor,
),
),
],
)
),
Positioned(
top: 10,
left: 10,
child: _buildInitButton()
)
]
)
],
),
)
);
}
Widget _buildInitButton() {
if(_isInitializing) {
return const CircularProgressIndicator(
color: primaryColor,
);
}
if(_initSucceeded) {
return ElevatedButton(
onPressed: null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
disabledForegroundColor: Colors.green,
),
child: const Text(
'Init Succeeded',
style: TextStyle(
color: primaryColor
),
),
);
}
return ElevatedButton(
onPressed: () {
_init();
},
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
),
child: const Text(
'Initialize Coffee',
style: TextStyle(
color: Colors.white
),
),
);
}
Marker _buildMarker({
required double latitude,
required double longitude,
required double heading,
Color color = primaryColor
}) {
return Marker(
width: 20.0,
height: 20.0,
point: LatLng(latitude, longitude),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
),
child: Transform.rotate(
angle: heading * (math.pi / 180),
child: Icon(Icons.navigation, color: color, size: 14),
),
),
);
}
Widget _buildLocationCard() {
String title = "Current Location";
if(_isTracking && _updatedLocationMarkers.isNotEmpty) {
title = "Tracking Location";
}
if(_currentLocation == null) {
return const SizedBox();
}
return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8), // Set your desired padding
color: Colors.black.withOpacity(0.6), // Set the background color
child: GestureDetector(
onTap: () {
setState(() {
_isLocationCardExpanded = !_isLocationCardExpanded;
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
Icon(
_isLocationCardExpanded ? Icons.expand_less : Icons.expand_more,
color: Colors.white,
)
],
),
)
),
SizedBox(height: _isLocationCardExpanded ? 10 : 0),
if (_isLocationCardExpanded)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_locationDetailRow('Latitude', _currentLocation!.latitude.toString()),
_locationDetailRow('Longitude', _currentLocation!.longitude.toString()),
_locationDetailRow('Accuracy', _currentLocation!.accuracy.toString()),
_locationDetailRow('Altitude', _currentLocation!.altitude.toString()),
_locationDetailRow('Heading', _currentLocation!.heading.toString()),
_locationDetailRow('Speed', _currentLocation!.speed.toString()),
_locationDetailRow('SpeeedAccuracy', _currentLocation!.speedAccuracy.toString()),
_locationDetailRow('BatteryLevel', _currentLocation!.batteryLevel.toString()),
_locationDetailRow('deviceVersion', _currentLocation!.deviceVersion.toString()),
_locationDetailRow('deviceName', _currentLocation!.deviceName.toString()),
_locationDetailRow('deviceType', _currentLocation!.deviceType.toString()),
_locationDetailRow('dateTime', _currentLocation!.dateTime.toString()),
_locationDetailRow('connectionType', _currentLocation!.internetConnectionType.toString())
],
),
]
)
);
}
Widget _locationDetailRow(String property, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
children: [
Text('$property: ', style: const TextStyle(color: Colors.white)),
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
],
),
);
}
}