Smart Location
A developer-friendly, DX-first Flutter package for handling location. It internally wraps geolocator with a clean, top-down designed API that minimizes boilerplate, manages permissions, and implements advanced client-side algorithms out of the box with zero third-party dependencies.
Features
🌟 Core Capabilities
- One-line Location Fetching: Get the current location safely.
- Continuous Tracking: Listen to location stream with simple configurations.
- Smart Permission Handling: Checks GPS and prompts permissions automatically.
- Pure Dart Models: Safe mapping from raw platform data to clean Dart classes.
🚀 Advanced Capabilities (New!)
- Client-Side Geofencing (Spatial Hash Optimized): Monitor geofences with entry, exit, and dwell triggers. Optimized with an $O(1)$ Spatial Hashing grid to support hundreds of fences without performance degradation.
- Route Recorder & Compressors: Record paths with real-time stats. Compress routes using the Ramer-Douglas-Peucker (RDP) algorithm, export to Google Encoded Polyline format, or generate standard GeoJSON outputs.
- Driver Safety (Speed & G-Force Monitor): Detect speed limit violations and identify sudden braking or acceleration events.
- Privacy Guard: Fuzz location coordinates within a randomized radius or snap to a coarse grid resolution to safeguard user privacy.
- Dead Reckoning Kinematic Engine: Extrapolate coordinates automatically using bearing and speed vector kinematics when GPS signals drop.
- Battery & Network Eco-Optimizer: Recommend adaptive tracking configurations to prolong device battery life.
- Indoor WiFi Centroid Locator: Estimate indoor coordinates by calculating a weighted centroid from surrounding access point RSSI strengths.
Getting Started
Add the dependency to your pubspec.yaml:
dependencies:
smart_location: ^2.0.1
🔥 Feature Spotlight: Headless Batched Sync
Unlike basic location wrappers that die when the app is swiped away, smart_location acts as a true enterprise tracking engine:
- Unkillable Tracking: It natively binds to Android Foreground Services and iOS Background Tasks to track forever.
- Offline Resilience: If the user enters a tunnel, locations are seamlessly saved to an offline SQLite database.
- Battery-Optimized Headless Sync: Waking up the Dart VM constantly drains batteries.
smart_locationuses AndroidWorkManagerand AppleBGTaskSchedulerto wake up a hidden Dart Isolate once every 15 minutes. You register a Dart callback, we wake it up, and you bulk-upload your offline coordinates to Firebase or your REST API seamlessly!
Platform Setup
Android
In android/app/src/main/AndroidManifest.xml, add inside <manifest>:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Background Location (optional):
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
iOS
In ios/Runner/Info.plist, add:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to provide better services.</string>
Background Location (optional):
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access for background tracking.</string>
Background Sync (required for Headless Batching):
In ios/Runner/Info.plist, you must register the background task identifier:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.antigravity.smart_location.sync</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>fetch</string>
<string>processing</string>
</array>
API Overview
Primary Methods
| API | Purpose |
|---|---|
SmartLocation.ensureReady() |
Request permission + verify GPS is on |
SmartLocation.current() |
One-time location fetch |
SmartLocation.lastKnown() |
Last cached location (no GPS needed) |
SmartLocation.stream |
Continuous live updates |
SmartLocation.streamWithSettings(...) |
Live updates with custom accuracy/filter |
SmartLocation.distanceBetween(...) |
Distance in meters between two points |
SmartLocation.bearingBetween(...) |
Direction/heading between two points |
SmartLocation.simplifyPath(...) |
Simplifies a list of coordinates using RDP |
Advanced Sub-Managers
| Sub-Manager | Class | Purpose |
|---|---|---|
SmartLocation.geofence |
GeofenceManager |
Client-side geofencing monitoring |
SmartLocation.recorder |
RouteRecorder |
Path recording & live session stats |
SmartLocation.speed |
SpeedMonitor |
Driver safety monitor (speeding, braking alerts) |
SmartLocation.privacy |
PrivacyGuard |
Coordinate fuzzer and snapped grid utilities |
SmartLocation.deadReckoning |
DeadReckoning |
Extrapolator watchdog during GPS signal drops |
SmartLocation.matching |
MapMatching |
Path smoothing & road segment snapping |
SmartLocation.eco |
EcoOptimizer |
Adaptive accuracy & distance configuration helper |
SmartLocation.indoor |
IndoorEmulator |
WiFi / Bluetooth beacon RSSI centroid tracking |
Usage
1. Initialize & Ensure Permissions
import 'package:smart_location/smart_location.dart';
// Checks GPS status and requests location permission from the user
await SmartLocation.ensureReady();
2. Get Current Location
try {
final loc = await SmartLocation.current();
print('Location: ${loc.latitude}, ${loc.longitude}');
} on LocationDisabledException {
print('Please turn on GPS');
} on PermissionDeniedException {
print('Permission was denied');
}
3. Listen to Location Stream
SmartLocation.stream.listen((loc) {
print('Moving to: ${loc.latitude}, ${loc.longitude}');
});
Advanced Features Usage
1️⃣ Client-Side Geofencing (Spatial Hash Optimized)
Efficiently monitors multiple circular geofences using a spatial grid. Emits transition events when entering, exiting, or dwelling in a zone.
// 1. Create a geofence with enter, exit, and dwell triggers
final geofence = Geofence(
id: 'office_HQ',
latitude: 37.7749,
longitude: -122.4194,
radiusInMeters: 100.0,
triggerTypes: {GeofenceTrigger.enter, GeofenceTrigger.exit, GeofenceTrigger.dwell},
dwellDuration: const Duration(minutes: 5),
);
// 2. Register and start monitoring
SmartLocation.geofence.addGeofence(geofence);
SmartLocation.geofence.startMonitoring(SmartLocation.stream);
// 3. Listen to transition events
SmartLocation.geofence.events.listen((event) {
print('Geofence Event [${event.geofenceId}]: ${event.triggerType}');
});
2️⃣ Route Recorder & Compression Utilities
Record user journeys, gather statistics (duration, distance, coordinates count), and format paths for mapping and storage optimization.
// Start recording the route
SmartLocation.recorder.start(SmartLocation.stream);
// Read live session statistics
print('Recorded Distance: ${SmartLocation.recorder.totalDistanceInMeters} m');
print('Elapsed Time: ${SmartLocation.recorder.elapsedTime}');
// Stop recording and retrieve path coordinates
final List<LocationData> path = SmartLocation.recorder.stop();
// Simplify the route path to reduce storage size (Ramer-Douglas-Peucker algorithm)
final List<LocationData> simplified = SmartLocation.simplifyPath(path, 10.0); // 10m tolerance
// Convert path to Google Encoded Polyline format
final String polyline = SmartLocation.maps.encodePolyline(simplified);
// Export coordinates to standard GeoJSON maps
final Map<String, dynamic> pointGeoJson = GeoJsonExporter.toPointMap(path.last);
final Map<String, dynamic> lineGeoJson = GeoJsonExporter.toLineStringMap(simplified);
3️⃣ Driver Safety Monitor (Speeding & Sudden G-Force Events)
Evaluates speed thresholds and detects sudden acceleration or harsh braking rates.
// Configure limits: speed limit and deceleration/acceleration rates
SmartLocation.speed.configure(
speedLimitKmh: 80.0,
suddenDecelThresholdKmhPerSec: 15.0, // harsh brake threshold
suddenAccelThresholdKmhPerSec: 10.0, // harsh acceleration threshold
);
// Start monitoring the GPS stream
SmartLocation.speed.startMonitoring(SmartLocation.stream);
// Receive alerts
SmartLocation.speed.speedAlerts.listen((alert) {
print('Speed Warning: ${alert.message}'); // Speed limit exceeded
});
SmartLocation.speed.gForceAlerts.listen((alert) {
print('G-Force Warning: ${alert.message}'); // Harsh braking or acceleration
});
4️⃣ Privacy Guard (Location Obfuscation)
Protects user identity and location patterns by adding a random displacement radius or snapping coordinates to a coarse degrees grid resolution.
final LocationData rawLocation = await SmartLocation.current();
// 1. Fuzz: Offset coordinates randomly within a 500-meter radius
final LocationData fuzzedLocation = PrivacyGuard.fuzz(rawLocation, radiusInMeters: 500.0);
// 2. Snap to Grid: Snaps coordinates to the nearest degree intersection (e.g. 0.01 degrees)
final LocationData snappedLocation = PrivacyGuard.snapToGrid(rawLocation, gridSizeDegrees: 0.01);
5️⃣ Dead Reckoning Fallback Engine
Keeps navigation UI fluent and responsive during GPS drops or indoor transitions by automatically extrapolating coordinates based on previous velocity and bearing heading vectors.
// Start the dead reckoning engine
SmartLocation.deadReckoning.start(
gpsStream: SmartLocation.stream,
onUpdate: (location) {
if (location.isMocked) {
print('Extrapolated Position: ${location.latitude}, ${location.longitude}');
} else {
print('GPS Raw Position: ${location.latitude}, ${location.longitude}');
}
},
staleThreshold: const Duration(seconds: 3), // triggers DR when GPS is stale for 3 seconds
extrapolationInterval: const Duration(seconds: 1),
);
// Stop engine when done
SmartLocation.deadReckoning.stop();
6️⃣ Battery & Network Eco-Optimizer
Implements power-saving strategies adaptively based on battery percent levels and connectivity states.
// Query optimal configuration recommendations
final LocationAccuracy accuracy = SmartLocation.eco.getOptimizedAccuracy(
batteryLevelPercent: 25.0,
hasNetwork: true,
);
final int filter = SmartLocation.eco.getOptimizedDistanceFilter(
batteryLevelPercent: 25.0,
hasNetwork: true,
);
// Listen to stream using recommended eco configurations
final ecoStream = SmartLocation.streamWithSettings(
accuracy: accuracy,
distanceFilter: filter,
);
7️⃣ Indoor WiFi / Beacon Centroid Locator
Calculates the device position indoors using the weighted average signal strengths of surrounding beacons or Wi-Fi access points.
// 1. Register indoor beacon/Wi-Fi locations
SmartLocation.indoor.registerBeacons({
'Lobby_AP': LocationData(latitude: 37.774900, longitude: -122.419400, accuracy: 2.0, altitude: 0, speed: 0, speedAccuracy: 0, heading: 0),
'ConfRoom_AP': LocationData(latitude: 37.775200, longitude: -122.419000, accuracy: 3.0, altitude: 0, speed: 0, speedAccuracy: 0, heading: 0),
});
// 2. Estimate position based on scanned signal levels (dBm RSSI)
final LocationData? estimatedLocation = SmartLocation.indoor.locateBySignals({
'Lobby_AP': -40.0,
'ConfRoom_AP': -90.0,
});
Error Handling
Always wrap location requests in a try-catch to handle disabled services and permission states:
try {
await SmartLocation.ensureReady();
final loc = await SmartLocation.current();
} on LocationDisabledException {
// GPS is OFF → prompt user to enable
} on PermissionDeniedException {
// Permission denied (can ask again)
} on PermissionPermanentlyDeniedException {
// Denied forever → open app settings
} catch (e) {
// Other unexpected errors
}
Best Practices
- Always call
ensureReady()before accessing location data. - Use
distanceFilterinstreamWithSettingsor utilizeEcoOptimizerrecommendations to save battery. - Use
lastKnown()to populate the UI instantly on startup. - Always handle all three permission exception types explicitly.
- Cancel stream subscriptions when they are no longer needed to prevent memory leaks.