ag_region_monitor 1.0.6
ag_region_monitor: ^1.0.6 copied to clipboard
Region Monitor plugin for Flutter, providing location-based event handling.
AgRegionMonitor Plugin #
A Flutter plugin for iOS that provides geofencing and region monitoring capabilities using CoreLocation. This plugin allows you to monitor when users enter or exit specific geographic regions, with support for customizable local notifications and real-time location updates.
Features #
- ✅ Geofencing - Monitor circular geographic regions
- ✅ Background Monitoring - Works when app is in background (with proper permissions)
- ✅ Custom Local Notifications - Personalized notifications with custom titles and messages
- ✅ Persistent Notification Settings - Notification preferences saved across app sessions
- ✅ Real-time Location Updates - Stream of location changes
- ✅ Permission Management - Handle location and notification permissions
- ✅ Multiple Regions - Monitor multiple geofences simultaneously
- ✅ Flexible Entry/Exit Monitoring - Configure notifications per region for entry and/or exit
- ✅ Region Management - Add, remove, and query active regions
Platform Support #
| Platform | Support |
|---|---|
| iOS | ✅ |
| Android | ❌ (Coming Soon) |
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
ag_region_monitor: ^1.0.3
Then run:
flutter pub get
iOS Setup #
1. Add Permissions to Info.plist #
Add these keys to your ios/Runner/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to monitor specific regions.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs background location access to monitor regions when the app is closed.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs always location access for geofencing.</string>
2. Enable Background Modes #
In Xcode, select your target → Capabilities → Background Modes, and enable:
- ✅ Location updates
- ✅ Background processing
Or add to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>background-processing</string>
</array>
Quick Start #
import 'package:ag_region_monitor/ag_region_monitor.dart';
class LocationService {
static Future<void> initializeLocationMonitoring() async {
// 1. Initialize the plugin
bool initialized = await AgRegionMonitor.initialize();
if (!initialized) {
print('Failed to initialize location manager');
return;
}
// 2. Request permissions
bool notificationPermission = await AgRegionMonitor.requestNotificationPermission();
String locationPermission = await AgRegionMonitor.checkLocationPermission();
print('Notification permission: $notificationPermission');
print('Location permission: $locationPermission');
// 3. Setup a geofence with custom notification
await AgRegionMonitor.setupGeofence(
latitude: 37.7749,
longitude: -122.4194,
radius: 100,
identifier: 'SanFranciscoOffice',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🏢 Welcome to the Office!',
notificationBody: 'You have entered the San Francisco office area. Check in to start your workday.',
);
// 4. Start monitoring
await AgRegionMonitor.startMonitoring();
// 5. Listen to region events
AgRegionMonitor.regionEvents.listen((event) {
print('Region event: $event');
});
}
}
API Reference #
Core Methods #
initialize()
Initialize the location manager and set up delegates.
Future<bool> initialize()
Example:
bool success = await AgRegionMonitor.initialize();
if (success) {
print('Location manager initialized successfully');
}
setupGeofence()
Create a circular geofence region for monitoring with customizable notifications.
Future<void> setupGeofence({
required double latitude,
required double longitude,
required double radius,
required String identifier,
bool notifyOnEntry = true,
bool notifyOnExit = false,
String? notificationTitle,
String? notificationBody,
})
Parameters:
latitude- Center latitude of the regionlongitude- Center longitude of the regionradius- Radius in meters (maximum: 1000m)identifier- Unique identifier for the regionnotifyOnEntry- Show notification when entering regionnotifyOnExit- Show notification when exiting regionnotificationTitle- NEW! Custom title for the notification (persisted across app sessions)notificationBody- NEW! Custom body text for the notification (persisted across app sessions)
Example:
await AgRegionMonitor.setupGeofence(
latitude: 40.7128,
longitude: -74.0060,
radius: 200,
identifier: 'NewYorkOffice',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🏙️ NYC Office Zone',
notificationBody: 'Welcome to the New York office. Tap to check your schedule and meetings.',
);
Custom Notification Examples:
// Home geofence
await AgRegionMonitor.setupGeofence(
latitude: 40.7589,
longitude: -73.9851,
radius: 50,
identifier: 'Home',
notifyOnEntry: true,
notificationTitle: '🏠 Welcome Home',
notificationBody: 'You\'ve arrived home safely. Time to relax!',
);
// Gym geofence
await AgRegionMonitor.setupGeofence(
latitude: 40.7505,
longitude: -73.9934,
radius: 75,
identifier: 'Gym',
notifyOnEntry: true,
notificationTitle: '💪 Workout Time!',
notificationBody: 'Ready to crush your fitness goals? Let\'s go!',
);
// School pickup zone
await AgRegionMonitor.setupGeofence(
latitude: 40.7282,
longitude: -73.7949,
radius: 100,
identifier: 'School',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🎒 School Zone',
notificationBody: 'You\'re at the school pickup area.',
);
startMonitoring()
Start location updates and region monitoring.
Future<void> startMonitoring()
Example:
await AgRegionMonitor.startMonitoring();
print('Started monitoring all regions');
stopMonitoring()
Stop monitoring a specific region.
Future<void> stopMonitoring(String identifier)
Example:
await AgRegionMonitor.stopMonitoring('NewYorkOffice');
stopAllMonitoring()
Stop monitoring all regions and location updates.
Future<void> stopAllMonitoring()
Example:
await AgRegionMonitor.stopAllMonitoring();
print('Stopped all monitoring');
Region Management Methods #
getActiveRegions()
Get all currently active/monitored regions with their details, including custom notification settings.
Future<List<Map<String, dynamic>>> getActiveRegions()
Returns: List of region data with identifier, latitude, longitude, radius, notifyOnEntry, notifyOnExit, and custom notification content
Example:
List<Map<String, dynamic>> regions = await AgRegionMonitor.getActiveRegions();
for (var region in regions) {
print('Region: ${region['identifier']} at (${region['latitude']}, ${region['longitude']})');
print('Radius: ${region['radius']}m, Entry: ${region['notifyOnEntry']}, Exit: ${region['notifyOnExit']}');
// Check for custom notification content
if (region.containsKey('notificationTitle')) {
print('Custom notification: ${region['notificationTitle']} - ${region['notificationBody']}');
}
}
removeRegion()
Remove a specific region by identifier and clean up its notification settings.
Future<bool> removeRegion(String identifier)
Returns: true if the region was successfully removed, false if not found
Example:
bool success = await AgRegionMonitor.removeRegion('NewYorkOffice');
if (success) {
print('Region and its notification settings removed successfully');
} else {
print('Region not found');
}
removeAllRegions()
Remove all regions from monitoring and clear all notification settings.
Future<bool> removeAllRegions()
Returns: true if all regions were successfully removed
Example:
bool success = await AgRegionMonitor.removeAllRegions();
if (success) {
print('All regions and notification settings removed');
}
Notification Control #
Enable or Disable local notifications which plugin handles internally
// Disable notifications globally
await AgRegionMonitor.setNotificationsEnabled(false);
// Re-enable notifications
await AgRegionMonitor.setNotificationsEnabled(true);
Repeat Notifications Control #
Enable or Disable repeat notifications which plugin handles internally
// Disable repeat notifications
await AgRegionMonitor.setNotificationsRepeatEnabled(false);
// Re-enable repeat notifications
await AgRegionMonitor.setNotificationsRepeatEnabled(true);
Repeat Notifications Timer #
Sets the repeat interval (in seconds) for triggering notifications after a user enters a monitored region.
By default, this is set to 300 seconds (5 minutes).
Notifications will repeat at the specified interval as long as the user remains within the region, and will automatically stop once the user exits the region.
// Set repeat notification interval to 60 seconds (1 minute)
await AgRegionMonitor.setNotificationsRepeatTimer(60);
getActiveRegionCount()
Get the count of currently active regions.
Future<int> getActiveRegionCount()
Example:
int count = await AgRegionMonitor.getActiveRegionCount();
print('Currently monitoring $count regions');
isRegionActive()
Check if a specific region is being monitored.
Future<bool> isRegionActive(String identifier)
Example:
bool isActive = await AgRegionMonitor.isRegionActive('NewYorkOffice');
if (isActive) {
print('Region is currently being monitored');
} else {
print('Region is not active');
}
getRegionById()
Get region details by identifier, including custom notification settings.
Future<Map<String, dynamic>?> getRegionById(String identifier)
Returns: Region data map or null if not found
Example:
Map<String, dynamic>? region = await AgRegionMonitor.getRegionById('NewYorkOffice');
if (region != null) {
print('Found region: ${region['identifier']}');
print('Location: (${region['latitude']}, ${region['longitude']})');
print('Radius: ${region['radius']}m');
// Check for custom notifications
if (region['notificationTitle'] != null) {
print('Custom notification: "${region['notificationTitle']}" - "${region['notificationBody']}"');
}
} else {
print('Region not found');
}
getActiveRegionIds()
Get all region identifiers that are currently being monitored.
Future<List<String>> getActiveRegionIds()
Example:
List<String> regionIds = await AgRegionMonitor.getActiveRegionIds();
print('Active regions: ${regionIds.join(', ')}');
// Check each region with its notification settings
for (String id in regionIds) {
Map<String, dynamic>? region = await AgRegionMonitor.getRegionById(id);
if (region != null) {
print('$id: (${region['latitude']}, ${region['longitude']})');
if (region['notificationTitle'] != null) {
print(' Notification: ${region['notificationTitle']}');
}
}
}
Permission Methods #
requestNotificationPermission()
Request permission to show local notifications.
Future<bool> requestNotificationPermission()
Example:
bool granted = await AgRegionMonitor.requestNotificationPermission();
if (granted) {
print('Notification permission granted');
} else {
print('Notification permission denied');
}
checkLocationPermission()
Check current location permission status.
Future<String> checkLocationPermission()
Returns: One of:
"notDetermined"- User hasn't been asked yet"denied"- User denied permission"restricted"- Permission is restricted"authorizedWhenInUse"- Permission granted when app is in use"authorizedAlways"- Permission granted always (required for geofencing)"unknown"- Unknown state
Example:
String status = await AgRegionMonitor.checkLocationPermission();
switch (status) {
case 'authorizedAlways':
print('Perfect! Can do background monitoring');
break;
case 'authorizedWhenInUse':
print('Limited permission - only when app is open');
break;
case 'denied':
print('Location permission denied');
break;
default:
print('Permission status: $status');
}
Convenience Methods #
hasLocationPermission()
Check if any location permission is granted.
Future<bool> hasLocationPermission()
hasAlwaysLocationPermission()
Check if "Always" location permission is granted (required for background geofencing).
Future<bool> hasAlwaysLocationPermission()
setupKarachiDangerZone()
Pre-configured geofence for Karachi with custom danger zone notification (example implementation).
Future<void> setupKarachiDangerZone()
Example:
// This sets up a danger zone in Karachi with custom notification
await AgRegionMonitor.setupKarachiDangerZone();
// Equivalent to:
// await AgRegionMonitor.setupGeofence(
// latitude: 24.8615,
// longitude: 67.0099,
// radius: 200,
// identifier: "KarachiDangerZone",
// notifyOnEntry: true,
// notifyOnExit: false,
// notificationTitle: "⚠️ Danger Zone",
// notificationBody: "You've entered a danger zone in Karachi!"
// );
Permission checking:
// Check permissions before setting up geofencing
bool hasPermission = await AgRegionMonitor.hasLocationPermission();
bool hasAlwaysPermission = await AgRegionMonitor.hasAlwaysLocationPermission();
if (!hasPermission) {
print('Need to request location permission first');
} else if (!hasAlwaysPermission) {
print('Need "Always" permission for background monitoring');
} else {
print('Ready for geofencing!');
}
Advanced Region Management #
Custom Notification Management
class NotificationManager {
// Setup regions with themed notifications
static Future<void> setupLocationBasedNotifications() async {
// Work locations
await AgRegionMonitor.setupGeofence(
latitude: 40.7589, longitude: -73.9851, radius: 100,
identifier: 'MainOffice',
notificationTitle: '💼 Work Mode Activated',
notificationBody: 'Welcome to the office! Your meetings and tasks are ready.',
);
// Personal locations
await AgRegionMonitor.setupGeofence(
latitude: 40.7505, longitude: -73.9934, radius: 75,
identifier: 'Gym',
notificationTitle: '🏃♂️ Workout Time!',
notificationBody: 'Time to achieve your fitness goals. You\'ve got this!',
);
// Family locations
await AgRegionMonitor.setupGeofence(
latitude: 40.7282, longitude: -73.7949, radius: 50,
identifier: 'Home',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🏠 Home Sweet Home',
notificationBody: 'Welcome home! Time to relax and unwind.',
);
// Important places with exit notifications
await AgRegionMonitor.setupGeofence(
latitude: 40.7614, longitude: -73.9776, radius: 200,
identifier: 'Hospital',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🏥 Hospital Area',
notificationBody: 'You are near the hospital. Drive carefully and keep quiet.',
);
}
// Update notification for existing region
static Future<void> updateRegionNotification(String identifier, String title, String body) async {
// Get current region details
Map<String, dynamic>? region = await AgRegionMonitor.getRegionById(identifier);
if (region != null) {
// Remove old region
await AgRegionMonitor.removeRegion(identifier);
// Re-create with new notification
await AgRegionMonitor.setupGeofence(
latitude: region['latitude'],
longitude: region['longitude'],
radius: region['radius'],
identifier: identifier,
notifyOnEntry: region['notifyOnEntry'],
notifyOnExit: region['notifyOnExit'],
notificationTitle: title,
notificationBody: body,
);
}
}
}
Region Management Examples #
// Complete region management workflow
class RegionManagementExample {
static Future<void> manageRegions() async {
// Setup multiple regions with custom notifications
await AgRegionMonitor.setupGeofence(
latitude: 40.7128, longitude: -74.0060, radius: 200,
identifier: 'NewYorkOffice',
notifyOnEntry: true, notifyOnExit: true,
notificationTitle: '🗽 NYC Office',
notificationBody: 'You\'ve arrived at the New York office!',
);
await AgRegionMonitor.setupGeofence(
latitude: 37.7749, longitude: -122.4194, radius: 150,
identifier: 'SanFranciscoOffice',
notifyOnEntry: true, notifyOnExit: false,
notificationTitle: '🌉 SF Office',
notificationBody: 'Welcome to San Francisco office!',
);
// Check what's active
int count = await AgRegionMonitor.getActiveRegionCount();
print('Total active regions: $count');
// List all region IDs
List<String> regionIds = await AgRegionMonitor.getActiveRegionIds();
print('Active region IDs: ${regionIds.join(', ')}');
// Get details for specific region including notifications
Map<String, dynamic>? nyOffice = await AgRegionMonitor.getRegionById('NewYorkOffice');
if (nyOffice != null) {
print('NY Office radius: ${nyOffice['radius']}m');
print('Notification: ${nyOffice['notificationTitle']} - ${nyOffice['notificationBody']}');
}
// Check if specific region is active
bool isNYActive = await AgRegionMonitor.isRegionActive('NewYorkOffice');
print('NY Office active: $isNYActive');
// Remove specific region (also removes its notification settings)
bool removed = await AgRegionMonitor.removeRegion('SanFranciscoOffice');
print('SF Office removed: $removed');
// Final count
count = await AgRegionMonitor.getActiveRegionCount();
print('Remaining regions: $count');
// Remove all regions and notification settings
await AgRegionMonitor.removeAllRegions();
print('All regions and notifications removed');
}
}
Event Streams #
regionEvents
Stream of region enter/exit events and monitoring failures.
Stream<Map<String, dynamic>> get regionEvents
Event Structure:
{
"event": "didEnterRegion" | "didExitRegion" | "monitoringDidFail",
"identifier": "region_identifier",
"timestamp": 1234567890.123,
"error": "error_message" // only for monitoringDidFail
}
Example:
late StreamSubscription regionSubscription;
void startListening() {
regionSubscription = AgRegionMonitor.regionEvents.listen((event) {
String eventType = event['event'];
String identifier = event['identifier'];
double timestamp = event['timestamp'];
DateTime eventTime = DateTime.fromMillisecondsSinceEpoch((timestamp * 1000).toInt());
switch (eventType) {
case 'didEnterRegion':
print('Entered region: $identifier at $eventTime');
_handleRegionEntry(identifier);
break;
case 'didExitRegion':
print('Exited region: $identifier at $eventTime');
_handleRegionExit(identifier);
break;
case 'monitoringDidFail':
String error = event['error'];
print('Monitoring failed for $identifier: $error');
_handleMonitoringError(identifier, error);
break;
}
});
}
void _handleRegionEntry(String identifier) {
// Custom logic for when user enters a region
// The notification is automatically shown by the plugin
switch (identifier) {
case 'Office':
// Start work mode, open productivity apps
break;
case 'Home':
// Enable home automation, set evening mood
break;
case 'Gym':
// Start workout tracking, play energetic music
break;
}
}
void stopListening() {
regionSubscription.cancel();
}
locationUpdates
Stream of location coordinate updates.
Stream<Map<String, dynamic>> get locationUpdates
Event Structure:
{
"latitude": 37.7749,
"longitude": -122.4194,
"timestamp": 1234567890.123
}
Example:
AgRegionMonitor.locationUpdates.listen((location) {
double lat = location['latitude'];
double lng = location['longitude'];
double timestamp = location['timestamp'];
DateTime updateTime = DateTime.fromMillisecondsSinceEpoch((timestamp * 1000).toInt());
print('Current location: $lat, $lng at $updateTime');
// Update your map or location display
updateMapLocation(lat, lng);
});
Complete Example #
import 'package:flutter/material.dart';
import 'package:ag_region_monitor/ag_region_monitor.dart';
import 'dart:async';
class GeofenceDemo extends StatefulWidget {
@override
_GeofenceDemoState createState() => _GeofenceDemoState();
}
class _GeofenceDemoState extends State<GeofenceDemo> {
String _status = 'Not initialized';
String _locationPermission = 'Unknown';
String _lastEvent = 'None';
int _activeRegionCount = 0;
List<String> _regionIds = [];
StreamSubscription? _regionSubscription;
StreamSubscription? _locationSubscription;
@override
void initState() {
super.initState();
_initializePlugin();
}
Future<void> _initializePlugin() async {
try {
// Initialize
bool initialized = await AgRegionMonitor.initialize();
if (!initialized) {
setState(() => _status = 'Failed to initialize');
return;
}
// Check permissions
String locationPermission = await AgRegionMonitor.checkLocationPermission();
bool notificationGranted = await AgRegionMonitor.requestNotificationPermission();
setState(() {
_status = 'Initialized';
_locationPermission = locationPermission;
});
// Setup geofence with custom notification
await AgRegionMonitor.setupGeofence(
latitude: 37.7749,
longitude: -122.4194,
radius: 100,
identifier: 'TestOffice',
notifyOnEntry: true,
notifyOnExit: true,
notificationTitle: '🏢 Test Office',
notificationBody: 'Welcome to the test office area! This is a custom notification.',
);
// Start monitoring
await AgRegionMonitor.startMonitoring();
// Update region info
await _updateRegionInfo();
// Listen to events
_regionSubscription = AgRegionMonitor.regionEvents.listen((event) {
setState(() {
_lastEvent = '${event['event']} - ${event['identifier']}';
});
_updateRegionInfo(); // Refresh region info on events
});
_locationSubscription = AgRegionMonitor.locationUpdates.listen((location) {
print('Location: ${location['latitude']}, ${location['longitude']}');
});
} catch (e) {
setState(() => _status = 'Error: $e');
}
}
Future<void> _updateRegionInfo() async {
int count = await AgRegionMonitor.getActiveRegionCount();
List<String> ids = await AgRegionMonitor.getActiveRegionIds();
setState(() {
_activeRegionCount = count;
_regionIds = ids;
});
}
@override
void dispose() {
_regionSubscription?.cancel();
_locationSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Geofence Demo')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status: $_status'),
SizedBox(height: 10),
Text('Location Permission: $_locationPermission'),
SizedBox(height: 10),
Text('Last Event: $_lastEvent'),
SizedBox(height: 10),
Text('Active Regions: $_activeRegionCount'),
SizedBox(height: 10),
Text('Region IDs: ${_regionIds.join(', ')}'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
String permission = await AgRegionMonitor.checkLocationPermission();
setState(() => _locationPermission = permission);
},
child: Text('Check Permission'),
),
ElevatedButton(
onPressed: () async {
await AgRegionMonitor.setupKarachiDangerZone();
await _updateRegionInfo();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Karachi danger zone setup!')),
);
},
child: Text('Setup Karachi Zone'),
),
ElevatedButton(
onPressed: () async {
List<Map<String, dynamic>> regions = await AgRegionMonitor.getActiveRegions();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Active Regions'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: regions.map((region) =>
Text('${region['identifier']}: (${region['latitude']}, ${region['longitude']}) - ${region['radius']}m')
).toList(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
},
child: Text('Show Region Details'),
),
ElevatedButton(
onPressed: () async {
bool success = await AgRegionMonitor.removeAllRegions();
await _updateRegionInfo();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('All regions removed: $success')),
);
},
child: Text('Remove All Regions'),
),
],
),
),
);
}
}
Best Practices #
1. Permission Handling #
Future<bool> ensurePermissions() async {
// Check current status
String locationStatus = await AgRegionMonitor.checkLocationPermission();
if (locationStatus == 'denied') {
// Show dialog explaining why permission is needed
_showPermissionDialog();
return false;
}
if (locationStatus != 'authorizedAlways') {
// Guide user to settings to enable "Always" permission
_showAlwaysPermissionGuide();
return false;
}
// Request notification permission
bool notificationGranted = await AgRegionMonitor.requestNotificationPermission();
return notificationGranted;
}
2. Error Handling #
Future<void> setupGeofenceWithRetry(double lat, double lng, String id) async {
try {
await AgRegionMonitor.setupGeofence(
latitude: lat,
longitude: lng,
radius: 100,
identifier: id,
);
} catch (e) {
print('Failed to setup geofence: $e');
// Implement retry logic or show user-friendly error
}
}
3. Resource Management #
class LocationManager {
StreamSubscription? _regionSub;
StreamSubscription? _locationSub;
void startListening() {
_regionSub = AgRegionMonitor.regionEvents.listen(_handleRegionEvent);
_locationSub = AgRegionMonitor.locationUpdates.listen(_handleLocationUpdate);
}
void dispose() {
_regionSub?.cancel();
_locationSub?.cancel();
AgRegionMonitor.stopAllMonitoring();
}
}
4. Region Management Best Practices #
class RegionManager {
// Setup regions with validation
static Future<bool> setupRegionSafely({
required double latitude,
required double longitude,
required double radius,
required String identifier,
bool notifyOnEntry = true,
bool notifyOnExit = false,
}) async {
// Check if region already exists
bool exists = await AgRegionMonitor.isRegionActive(identifier);
if (exists) {
print('Region $identifier already exists');
return false;
}
// Check region count limit (iOS supports ~20 regions)
int count = await AgRegionMonitor.getActiveRegionCount();
if (count >= 19) {
print('Too many regions active. Current: $count');
return false;
}
try {
await AgRegionMonitor.setupGeofence(
latitude: latitude,
longitude: longitude,
radius: radius,
identifier: identifier,
notifyOnEntry: notifyOnEntry,
notifyOnExit: notifyOnExit,
);
return true;
} catch (e) {
print('Failed to setup region: $e');
return false;
}
}
// Clean up specific regions
static Future<void> cleanupOldRegions(List<String> regionsToKeep) async {
List<String> activeIds = await AgRegionMonitor.getActiveRegionIds();
for (String id in activeIds) {
if (!regionsToKeep.contains(id)) {
bool removed = await AgRegionMonitor.removeRegion(id);
print('Removed old region $id: $removed');
}
}
}
}
Manual Location Check Functions #
Overview #
The Manual Location Check functionality allows you to programmatically check if specific coordinates fall within any of your active geofence regions. This is useful for scenarios where you want to validate locations without relying on automatic location updates.
Functions #
checkManualLocation #
The primary function for checking if coordinates fall within active geofence regions.
Signature
static Future<List<Map<String, dynamic>>> checkManualLocation({
required double latitude,
required double longitude,
})
Parameters
latitude(required): The latitude coordinate to checklongitude(required): The longitude coordinate to check
Returns
A Future<List<Map<String, dynamic>>> containing all regions that contain the specified coordinates. Each region object includes:
| Field | Type | Description |
|---|---|---|
identifier |
String |
Unique identifier for the region |
latitude |
double |
Center latitude of the region |
longitude |
double |
Center longitude of the region |
radius |
double |
Radius of the region in meters |
notifyOnEntry |
bool |
Whether notifications are enabled for entry |
notifyOnExit |
bool |
Whether notifications are enabled for exit |
distance |
double |
Distance from input coordinates to region center (in meters) |
notificationTitle |
String? |
Custom notification title (if set) |
notificationBody |
String? |
Custom notification body (if set) |
Behavior
- Calculates distance from input coordinates to each active region's center
- If distance ≤ radius, the region is considered a match
- Automatically sends notifications for all matching regions
- Returns detailed information about all matching regions
Example
final matchingRegions = await AgRegionMonitor.checkManualLocation(
latitude: 24.8615,
longitude: 67.0099,
);
print('Found ${matchingRegions.length} matching regions');
for (var region in matchingRegions) {
print('Region: ${region['identifier']}');
print('Distance: ${region['distance'].toStringAsFixed(2)} meters');
print('Radius: ${region['radius']} meters');
}
isLocationInAnyRegion #
A convenience function that returns a boolean indicating whether coordinates fall within any active region.
Signature
static Future<bool> isLocationInAnyRegion({
required double latitude,
required double longitude,
})
Parameters
latitude(required): The latitude coordinate to checklongitude(required): The longitude coordinate to check
Returns
A Future<bool> that resolves to:
trueif the coordinates fall within at least one active regionfalseif the coordinates don't fall within any active region
Behavior
- Internally calls
checkManualLocation - Will trigger notifications for matching regions
- Returns
trueif any regions match
Example
final isInDangerZone = await AgRegionMonitor.isLocationInAnyRegion(
latitude: 24.8615,
longitude: 67.0099,
);
if (isInDangerZone) {
print('Location is within a monitored region');
} else {
print('Location is safe');
}
getRegionsContainingLocation #
An alias for checkManualLocation that provides semantic clarity for read-only operations.
Signature
static Future<List<Map<String, dynamic>>> getRegionsContainingLocation({
required double latitude,
required double longitude,
})
Parameters
latitude(required): The latitude coordinate to checklongitude(required): The longitude coordinate to check
Returns
Same as checkManualLocation - a list of matching region objects.
Behavior
- Currently identical to
checkManualLocation - Will trigger notifications for matching regions
- Future versions may support read-only mode
Example
final regions = await AgRegionMonitor.getRegionsContainingLocation(
latitude: 24.8615,
longitude: 67.0099,
);
for (var region in regions) {
print('Found in region: ${region['identifier']}');
}
Usage Patterns #
Basic Location Validation #
// Check if a user-entered address is in a danger zone
final coordinates = await geocodeAddress(userAddress);
final isInDangerZone = await AgRegionMonitor.isLocationInAnyRegion(
latitude: coordinates.latitude,
longitude: coordinates.longitude,
);
Detailed Region Analysis #
// Get detailed information about which regions contain a location
final regions = await AgRegionMonitor.checkManualLocation(
latitude: 24.8615,
longitude: 67.0099,
);
for (var region in regions) {
final distance = region['distance'] as double;
final radius = region['radius'] as double;
final percentage = (distance / radius * 100).toStringAsFixed(1);
print('${region['identifier']}: ${percentage}% into region');
}
Bulk Location Processing #
// Check multiple locations
final locations = [
{'lat': 24.8615, 'lng': 67.0099, 'name': 'Location A'},
{'lat': 24.8700, 'lng': 67.0200, 'name': 'Location B'},
];
for (var location in locations) {
final isInRegion = await AgRegionMonitor.isLocationInAnyRegion(
latitude: location['lat'],
longitude: location['lng'],
);
print('${location['name']}: ${isInRegion ? 'ALERT' : 'Safe'}');
}
Important Notes #
Notifications #
- ⚠️ All manual location check functions will trigger notifications if the coordinates fall within active regions
- Notifications respect the current notification settings (
notificationsEnabled) - Custom notification titles and bodies are used if configured for the region
Performance #
- Functions perform distance calculations for all active regions
- Consider caching results if checking the same coordinates repeatedly
- No limit on the number of regions that can match
Accuracy #
- Uses standard geographic distance calculations (haversine formula via CoreLocation)
- Accuracy depends on the precision of input coordinates
- Results are in meters
Error Handling #
try {
final regions = await AgRegionMonitor.checkManualLocation(
latitude: invalidLat,
longitude: invalidLng,
);
} catch (e) {
print('Error checking location: $e');
// Handle error (invalid coordinates, plugin not initialized, etc.)
}
Prerequisites #
- Plugin must be initialized with
AgRegionMonitor.initialize() - At least one geofence region must be active
- Notification permissions recommended for full functionality
Limitations #
- iOS Only: Currently only supports iOS. Android support is planned.
- Maximum Radius: iOS limits geofence radius to 1000 meters.
- Region Limit: iOS typically supports monitoring up to 20 regions simultaneously.
- Background Processing: Requires "Always" location permission for background monitoring.
- Battery Impact: Continuous location monitoring may impact battery life.
Troubleshooting #
Common Issues #
-
Geofencing not working in background
- Ensure "Always" location permission is granted
- Check that Background Modes are enabled in Xcode
-
Notifications not showing
- Verify notification permissions are granted
- Check that the app is not in Do Not Disturb mode
-
Permission dialogs not appearing
- Make sure Info.plist contains the required usage descriptions
- Check that you're calling initialize() before other methods
-
Events not firing
- Verify the device has good GPS signal
- Test with larger radius first (200m+)
- Check that you're listening to the event stream
-
Region management issues
- Use
getActiveRegionCount()to check region limits - Use
isRegionActive()to avoid duplicate regions - Use
getActiveRegionIds()to debug what's currently monitored
- Use
Debug Tips #
// Enable verbose logging
AgRegionMonitor.regionEvents.listen((event) {
print('DEBUG - Region Event: $event');
});
AgRegionMonitor.locationUpdates.listen((location) {
print('DEBUG - Location: ${location['latitude']}, ${location['longitude']}');
});
// Check permission status regularly
Timer.periodic(Duration(seconds: 30), (timer) async {
String status = await AgRegionMonitor.checkLocationPermission();
print('Permission status: $status');
});
// Debug region management
Future<void> debugRegions() async {
int count = await AgRegionMonitor.getActiveRegionCount();
List<String> ids = await AgRegionMonitor.getActiveRegionIds();
List<Map<String, dynamic>> regions = await AgRegionMonitor.getActiveRegions();
print('=== REGION DEBUG ===');
print('Active count: $count');
print('Active IDs: ${ids.join(', ')}');
for (var region in regions) {
print('${region['identifier']}: (${region['latitude']}, ${region['longitude']}) radius: ${region['radius']}m');
print(' Entry: ${region['notifyOnEntry']}, Exit: ${region['notifyOnExit']}');
}
print('==================');
}
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.