ag_region_monitor 1.0.7 copy "ag_region_monitor: ^1.0.7" to clipboard
ag_region_monitor: ^1.0.7 copied to clipboard

PlatformiOS

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 region
  • longitude - Center longitude of the region
  • radius - Radius in meters (maximum: 1000m)
  • identifier - Unique identifier for the region
  • notifyOnEntry - Show notification when entering region
  • notifyOnExit - Show notification when exiting region
  • notificationTitle - 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 check
  • longitude (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 check
  • longitude (required): The longitude coordinate to check

Returns

A Future<bool> that resolves to:

  • true if the coordinates fall within at least one active region
  • false if the coordinates don't fall within any active region

Behavior

  • Internally calls checkManualLocation
  • Will trigger notifications for matching regions
  • Returns true if 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 check
  • longitude (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 #

  1. Geofencing not working in background

    • Ensure "Always" location permission is granted
    • Check that Background Modes are enabled in Xcode
  2. Notifications not showing

    • Verify notification permissions are granted
    • Check that the app is not in Do Not Disturb mode
  3. Permission dialogs not appearing

    • Make sure Info.plist contains the required usage descriptions
    • Check that you're calling initialize() before other methods
  4. 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
  5. Region management issues

    • Use getActiveRegionCount() to check region limits
    • Use isRegionActive() to avoid duplicate regions
    • Use getActiveRegionIds() to debug what's currently monitored

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.

2
likes
140
points
28
downloads

Publisher

unverified uploader

Weekly Downloads

Region Monitor plugin for Flutter, providing location-based event handling.

Repository (GitHub)
View/report issues

Documentation

API reference

License

GPL-3.0 (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on ag_region_monitor

Packages that implement ag_region_monitor