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: ^0.0.5

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>

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 distanceFilter in streamWithSettings or utilize EcoOptimizer recommendations 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.

Libraries

smart_location