smart_location_plus 0.2.0
smart_location_plus: ^0.2.0 copied to clipboard
A unified Flutter location package combining geolocator, geocoding, and native geofencing into one simple, powerful API. Battery-efficient, platform-native geofencing. Zero boilerplate, full customization.
import 'package:flutter/material.dart';
import 'package:smart_location_plus/smart_location_plus.dart';
void main() => runApp(const SmartLocationExampleApp());
class SmartLocationExampleApp extends StatelessWidget {
const SmartLocationExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SmartLocation Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ExampleHome(),
);
}
}
class ExampleHome extends StatefulWidget {
const ExampleHome({super.key});
@override
State<ExampleHome> createState() => _ExampleHomeState();
}
class _ExampleHomeState extends State<ExampleHome> {
String _output = 'Tap a button to try a feature.';
bool _loading = false;
void _log(String msg) => setState(() => _output = msg);
void _setLoading(bool v) => setState(() => _loading = v);
// ── 1. locateOnce ──────────────────────────────────────────────────────
Future<void> _locateOnce() async {
_setLoading(true);
try {
final loc = await SmartLocation.locateOnce();
_log('📍 locateOnce()\n'
'Lat: ${loc.latitude}\n'
'Lng: ${loc.longitude}\n'
'Address: ${loc.address ?? "not resolved"}\n'
'City: ${loc.city}\n'
'Accuracy: ${loc.accuracy.toStringAsFixed(1)} m');
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
// ── 1b. locateOnce — high accuracy, Hindi address ──────────────────────
Future<void> _locateOnceCustom() async {
_setLoading(true);
try {
final loc = await SmartLocation.locateOnce(
locationConfig: LocationConfig(
accuracy: LocationAccuracy.best,
timeLimit: const Duration(seconds: 10),
),
geocodingConfig: const GeocodingConfig(
localeIdentifier: 'hi',
maxResults: 1,
),
);
_log('📍 locateOnce() — high accuracy, Hindi\n'
'Lat: ${loc.latitude}\n'
'Lng: ${loc.longitude}\n'
'Address (hi): ${loc.address ?? "—"}');
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
// ── 2. locateAndWatch ─────────────────────────────────────────────────
Stream<LocationResult>? _watchStream;
void _startWatch() {
_watchStream = SmartLocation.locateAndWatch(
locationConfig: LocationConfig(
accuracy: LocationAccuracy.high,
distanceFilter: 5,
),
geocodingConfig: const GeocodingConfig(resolveAddress: false),
);
_watchStream!.listen(
(loc) => _log('🔄 locateAndWatch()\n'
'Lat: ${loc.latitude}\n'
'Lng: ${loc.longitude}\n'
'Speed: ${(loc.speed * 3.6).toStringAsFixed(1)} km/h'),
onError: (e) => _log('❌ Stream error: $e'),
);
_log('▶️ Watching location... move device to see updates.');
}
// ── 3. Last known location ─────────────────────────────────────────────
Future<void> _lastKnown() async {
_setLoading(true);
try {
final loc = await SmartLocation.getLastKnownLocation();
if (loc == null) {
_log('⚠️ No last known location cached.');
} else {
_log('🕓 Last Known Location\n'
'Lat: ${loc.latitude}\n'
'Lng: ${loc.longitude}\n'
'Time: ${loc.timestamp}');
}
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
// ── 4. Reverse geocode ─────────────────────────────────────────────────
Future<void> _reverseGeocode() async {
_setLoading(true);
try {
final result = await SmartLocation.reverseGeocode(
latitude: 18.9220,
longitude: 72.8347,
config: const GeocodingConfig(localeIdentifier: 'en_IN'),
);
_log('🗺️ reverseGeocode(18.9220, 72.8347)\n'
'Address: ${result.address}\n'
'Street: ${result.street}\n'
'City: ${result.city}\n'
'State: ${result.state}\n'
'Country: ${result.country}\n'
'PIN: ${result.postalCode}');
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
// ── 5. Forward geocode ─────────────────────────────────────────────────
Future<void> _forwardGeocode() async {
_setLoading(true);
try {
final result = await SmartLocation.forwardGeocode(
address: 'Gateway of India, Mumbai',
config: const GeocodingConfig(maxResults: 2),
);
_log('🔍 forwardGeocode("Gateway of India, Mumbai")\n'
'Lat: ${result.latitude}\n'
'Lng: ${result.longitude}\n'
'Total results: ${result.allResults.length}');
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
// ── 6. Geofencing ──────────────────────────────────────────────────────
Future<void> _startGeofence() async {
_setLoading(true);
try {
await SmartLocation.watchZone(
zones: [
LocationZone(
id: 'home',
latitude: 19.2183,
longitude: 73.0587,
config: const GeofenceConfig(
radius: 100,
loiteringDelay: 30000,
listenFor: {
GeofenceEvent.enter,
GeofenceEvent.exit,
GeofenceEvent.dwell,
},
),
onEnter: (zone) => _log('✅ Entered zone: ${zone.id}'),
onExit: (zone) => _log('🚪 Exited zone: ${zone.id}'),
onDwell: (zone) => _log('⏳ Dwelling in zone: ${zone.id}'),
),
LocationZone(
id: 'office',
latitude: 19.3000,
longitude: 73.0700,
config: const GeofenceConfig(radius: 200),
onEnter: (zone) => _log('🏢 Arrived at office!'),
onExit: (zone) => _log('👋 Left office'),
),
],
globalConfig: GeofenceConfig(
useActivityRecognition: true,
allowMockLocations: true,
printDevLog: true,
onError: (e) => _log('❌ Geofence error: $e'),
),
);
_log('📡 Geofencing started!\n'
'Monitoring ${SmartLocation.activeZones.length} zones:\n'
'• home (r: 100m)\n'
'• office (r: 200m)');
} catch (e) {
_log('❌ Error: $e');
} finally {
_setLoading(false);
}
}
Future<void> _stopGeofence() async {
await SmartLocation.stopWatchingZones();
_log('🛑 Geofencing stopped.');
}
// ── 7. Utilities ───────────────────────────────────────────────────────
void _distance() {
final metres = SmartLocation.distanceBetween(
startLat: 19.2183, startLng: 73.0587,
endLat: 19.3000, endLng: 73.0700,
);
final km = (metres / 1000).toStringAsFixed(2);
_log('📏 distanceBetween()\n'
'${metres.toStringAsFixed(0)} metres (~$km km)\n'
'From: home (19.2183, 73.0587)\n'
'To: office (19.3000, 73.0700)');
}
void _bearing() {
final deg = SmartLocation.bearingBetween(
startLat: 19.2183, startLng: 73.0587,
endLat: 19.3000, endLng: 73.0700,
);
_log('🧭 bearingBetween()\n'
'${deg.toStringAsFixed(1)}° from home to office');
}
void _isInside() {
final inside = SmartLocation.isInsideZone(
latitude: 19.2185,
longitude: 73.0589,
zone: LocationZone(
id: 'home',
latitude: 19.2183,
longitude: 73.0587,
config: const GeofenceConfig(radius: 100),
),
);
_log('📌 isInsideZone()\n'
'Point (19.2185, 73.0589) is ${inside ? "INSIDE ✅" : "OUTSIDE ❌"} '
'the home zone (r: 100m)');
}
// ── 8. Permissions ──────────────────────────────────────────────────────
Future<void> _checkPermission() async {
final status = await SmartLocation.checkPermission();
_log('🔐 Permission: $status');
}
// ── Build ───────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SmartLocation Demo'),
centerTitle: true,
),
body: Column(
children: [
// Output box
Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.indigo.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.indigo.shade200),
),
child: _loading
? const Center(child: CircularProgressIndicator())
: Text(
_output,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 13,
height: 1.6,
),
),
),
// Buttons
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
_section('📍 Location'),
_btn('locateOnce() — defaults', _locateOnce),
_btn('locateOnce() — high accuracy + Hindi', _locateOnceCustom),
_btn('locateAndWatch() — start stream', _startWatch),
_btn('getLastKnownLocation()', _lastKnown),
_section('🗺️ Geocoding'),
_btn('reverseGeocode(18.92, 72.83)', _reverseGeocode),
_btn('forwardGeocode("Gateway of India")', _forwardGeocode),
_section('📡 Geofencing'),
_btn('watchZone() — start (home + office)', _startGeofence),
_btn('stopWatchingZones()', _stopGeofence),
_section('🛠️ Utilities'),
_btn('distanceBetween(home → office)', _distance),
_btn('bearingBetween(home → office)', _bearing),
_btn('isInsideZone()', _isInside),
_section('🔐 Permissions'),
_btn('checkPermission()', _checkPermission),
_btn('openLocationSettings()', () async {
await SmartLocation.openLocationSettings();
}),
_btn('openAppSettings()', () async {
await SmartLocation.openAppSettings();
}),
const SizedBox(height: 24),
],
),
),
],
),
);
}
Widget _section(String title) => Padding(
padding: const EdgeInsets.only(top: 16, bottom: 4),
child: Text(
title,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.indigo.shade700,
letterSpacing: 0.5,
),
),
);
Widget _btn(String label, VoidCallback onTap) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: FilledButton.tonal(
onPressed: _loading ? null : onTap,
child: Align(
alignment: Alignment.centerLeft,
child: Text(label),
),
),
);
}