Skip to content

[Help Wanted]: Support Needed for geofence in ios and android #1527

@safwanidrees

Description

@safwanidrees

Required Reading

  • Confirmed

Plugin Version

4.16.11

Mobile operating-system(s)

  • iOS
  • Android

Device Manufacturer(s) and Model(s)

Samsung Galaxy s22, Iphone 15 Pro

Device operating-systems(s)

iOS 18.5, Android 15

What do you require assistance about?

We have purchased the paid version of the flutter_background_geolocation package and integrated it into our Flutter app.

We are using the package strictly for geofencing purposes, with the following requirements:

Geofence events must trigger in foreground, background, and terminated states.

Events must trigger with or without internet.

The geofence zones represent (e.g., building).

Upon entering/exiting a zone, we execute several functions, such as sending notifications, writing to Hive DB, api call, and calling native method channels.

Usage Flow

  1. User logs in, and we fetch geofence zones from the backend.
  2. Request required permissions.
  3. Start a listener for geofence events.
  4. Register a headless task for terminated state handling on android.
  5. Dynamically add/remove geofences from the device.
  6. Start the package using a tailored config.
  7. Initial triggered using geolocation, current location and turf to see if user in geofence or not.

Questions & Support Needed

1- In real-world testing, we noticed significant battery drain. For example, the device dropped from 30% to 9% in 3 hours with no other app usage. Could you help us optimize our configuration to reduce battery impact?

2- We do not require the package to make any HTTP requests. Can we safely disable all related sync/configs?

3- Your documentation mentions that iOS can relaunch the app in the background when a geofence event occurs in the terminated state. When this happens: Does the full app lifecycle run (e.g., main(), initState() etc.)? Where exactly should we place our logic to ensure our onGeofenceTriggered function runs correctly?

4- Could you kindly review the configuration (shared in startGeofenceService) and advise any improvements specifically for: 24/7 geofence monitoring (foreground/background/terminated) and Only geofencing

5- Is it possible to suppress the persistent notification shown when geolocation service runs in the on Android?

6- If the user disables location services while the app is terminated, can we detect this condition when the app wakes via provider change? and trigger a custom notification and execute a function (like we do for geofence events)?

7- When the user restarts the phone, we want to trigger the event again. How can we handle that?

8- Does this package have features like recurring rules?. Triggered event only if those recurring rules match?

[Optional] Plugin Code and/or Config

🔄 Usage Flow with code

1-  User logs in, and we fetch geofence zones from the backend.

2-  Request required permissions.

3-  Start a listener for geofence events.


///Start live location updates

 void startLiveLocationUpdates(List<GeoJsonArea> areas) {

   _backgroundGeolocation.onGeofence(

     (geofenceEvent) async {

       final rrule = geofenceEvent.extras?['recurringRule'] as String?;

       if (rrule != null) {

         var trigger = false;

         // If the event is enter, check if the time matches the recurring rule

         if (geofenceEvent.action.event == GeoFenceEvent.enter) {

           trigger = rrule.check();

         }  



         if (trigger) {

           final event = GeoFenseEventTrigger(

             event: geofenceEvent.action.event,

             name: geofenceEvent.identifier,

             coordinate: Coordinate(

               longitude: geofenceEvent.location.coords.longitude,

               latitude: geofenceEvent.location.coords.latitude,

             ),

             time: DateTime.now(),

           );

           _lastEvent = event;

          

           await _geoFencingForegroundRepository.onGenfenceTriggered(

             event,

           );

         }

       }

     },

   );

 }


4- Register a headless task for terminated state handling on android.


@pragma('vm:entry-point')

Future<void> backgroundGeolocationHeadlessTask(

 bg.HeadlessEvent headlessEvent,

) async {

 switch (headlessEvent.name) {

   case bg.Event.GEOFENCE:

     final geofenceEvent = headlessEvent.event as bg.GeofenceEvent;



     try {

       final rrule = geofenceEvent.extras?['recurringRule'] as String?;

       if (rrule != null) {

         if (geofenceEvent.action.event == GeoFenceEvent.enter) {

           final isMatch = rrule.check();

           if (isMatch) {

             await onGeofenceTriggered(geofenceEvent);

           }

         } else {

           await onGeofenceTriggered(geofenceEvent);

         }

       }

     } catch (e) {

       _log.shoutIfEnabled(() => 'Error in geofence event: $e');

       debugPrint('Error in geofence event: $e');

     }

   case bg.Event.PROVIDERCHANGE:

     final providerChangeEvent = headlessEvent.event as bg.ProviderChangeEvent;

     if (providerChangeEvent.gpsOff()) {

       try {

         await backgroundContainerInit();

         await globalContainer

             .read(localNotificationServiceProvider)

             .displayNotification(

               id: notificationPermissionRemovedId,

               title: TextConstants.notificationPermissionRemovedTitle,

               body: TextConstants.notificationPermissionRemovedBody,

               payload: 'location_services_disabled',

             );

       } catch (e) {

         _log.shoutIfEnabled(() => 'Error in provider change event: $e');

         debugPrint('Error in provider change event: $e');

       }

     }

 }

}

5- Dynamically add/remove geofences from the device.

 

Future<void> init(List<GeoJsonArea> areas) async {

   try {

     final alreadyAddedZones = await _backgroundGeolocation.geofences;

     final areasToAdd = areas.areasToAddToGeoFense(alreadyAddedZones);

     final areasToRemove = areas.areasToRemoveFromGeoFense(alreadyAddedZones);



     await _removeGeofenceZones(areasToRemove);

     await _addGeofenceZones(areasToAdd);



     _log.infoIfEnabled(

       () => 'GeoFencingForegroundService init completed',

     );

   } catch (e) {

     _log.shoutIfEnabled(

       e.toString,

     );

   }

 }


6- Start the package using a tailored config.


Future<void> startGeofenceService() async {

   try {

     final configuration = _applicationconfigurationProvider

         .getJson(ConfigurationKeys.localNotification);



     final groupChannelId = configuration.getAndEnsure('group_channel_id');

     final groupChannelName = configuration.getAndEnsure('group_channel_name');



     final state = await _backgroundGeolocation.ready(

       bg.Config(

         notification: bg.Notification(

           channelName: groupChannelName,

           channelId: groupChannelId,

           smallIcon: 'mipmap/ic_launcher',

           title: TextConstants.notificationMainTitle,

           priority: bg.Config.NOTIFICATION_PRIORITY_MIN,

         ),

         locationAuthorizationAlert: {

           'titleWhenNotEnabled': TextConstants.locationServicesDisabled,

           'titleWhenOff': TextConstants.locationServicesDisabled,

           'instructions': TextConstants.pleaseEnableLocationServices,

           'cancelButton': TextConstants.cancel,

           'settingsButton': TextConstants.settings,

         },

         autoSync: true,

         geofenceModeHighAccuracy: true,

         desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,

         distanceFilter: 10,

         debug: AppRunnerMode.instance.isDebugMode,

         logLevel: bg.Config.LOG_LEVEL_VERBOSE,

         stopTimeout: 5,

         geofenceProximityRadius: 1000,

         locationAuthorizationRequest: 'Always',

         enableHeadless: true,

         heartbeatInterval: 60,

         stopOnTerminate: false,

         startOnBoot: true,

         geofenceInitialTriggerEntry: true,

         reset: true,

         isMoving: true,

         showsBackgroundLocationIndicator: false,

       ),

     );

     if (!state.enabled) {

       await _backgroundGeolocation.startGeofences();

     }

   } catch (e) {

     debugPrint('Error: $e');

   }

 }



7- Initial triggered using geolocation current location and turf to see if user in geofence or not


Future<void> initialTriggered(List<GeoJsonArea> areas) async {

   try {
     // using geolocator
     final location = await _backgroundGeolocation.getCurrentPosition();



     final insideArea = areas.currentGeoFence(

       location.latitude,

       location.longitude,

     );

     if (insideArea == null) {

       return;

     }

     final rrule = insideArea.recurringRule;

     if (rrule != null) {

       final isMatch = rrule.check();

       if (isMatch) {

         _lastEvent = GeoFenseEventTrigger(

           event: GeoFenceEvent.enter,

           name: insideArea.name ?? '',

           coordinate: Coordinate(

             longitude: location.longitude,

             latitude: location.latitude,

           ),

           time: DateTime.now(),

         );

     

         await _geoFencingForegroundRepository.onGenfenceTriggered(

           _lastEvent!,

         );

       }

     }

   } catch (e) {

     _log.shoutIfEnabled(() => 'Initial Location Trigger Error: $e');

   }

 }

Ios Info.plist

  <key>UIBackgroundModes</key>

       <array>

           <string>location</string>

           <string>fetch</string>

           <string>processing</string>

       </array>

[Optional] Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions