geofencing_flutter_plugin
This Flutter plugin extends the functionality offered by the Woosmap Geofencing Mobile SDKs. Find more about the Woosmap Geofencing SDK.
| Android | iOS | |
|---|---|---|
| Support SDK | 35+ | 15.0+ |
Supported Platforms
- iOS
- Android
Modules
- GeofencingFlutterPlugin: Contains methods to monitor location, regions and POIs.
Objects
- Location: Represents the location object.
- POI: Represents Point of Interest.
- Region: Represents a geographical region/geofence.
- GeofenceRegion: Used while adding a new region in the local database.
Types
- ProfileSource: Represents tracking profile source. It can either be
ProfileSource.localandProfileSource.external - RegionType: Represents the type of the region. Possible values are
RegionType.circleandRegionType.isochrone - GeofenceRegionCallback: Signature of the function woken in a background isolate for a region event. See Background region events.
Usage
import 'package:geofencing_flutter_plugin/geofencing_flutter_plugin.dart';
final geofencingFlutterPlugin = GeofencingFlutterPlugin();
// ...
Check and request permissions
Before initializing the SDK it is required that you request for required location permissions.
To check if the location permissions are granted by the user call getPermissionsStatus method.
Future<String?> returnVal = geofencingFlutterPlugin.getPermissionsStatus();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Parameter status will be a string, one of:
GRANTED_BACKGROUND: User has granted location access even when app is not running in the foreground.GRANTED_FOREGROUND: Location access is granted only while user is using the app.DENIED: Location access is denied.UNKNOWN: Without providing or denying any permission then it will return unknown.
Please note: Plugin will not work as expected if location access is denied.
Requesting location access
To request location access call requestPermissions method of the plugin. This will result in displaying location access permission dialog. This method accepts a boolean parameter withBackgroundAccess. If this parameter is set to true, then plugin will ask for background location access. Code snippet below asks for background location access.
Android grants background location in two steps: if foreground access is not yet granted, the plugin requests it first and returns GRANTED_FOREGROUND; call requestPermissions(withBackgroundAccess: true) again to request background access, which on Android 11+ opens the app's location settings so the user can select "Allow all the time".
Future<String?> returnVal = geofencingFlutterPlugin.requestPermissions(withBackgroundAccess:true/false);
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Check Bluetooth permissions
In order to get beacon-fencing running on Android devices you need to make sure that all the Bluetooth related permissions are granted to the plugin.
To check if the Bluetooth permissions are granted by the user call getBLEPermissionsStatus method.
Future<String?> returnVal = geofencingFlutterPlugin.getBLEPermissionsStatus();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Parameter status will be a string, one of:
GRANTED: User has granted bluetooth access.DENIED: Bluetooth access is denied.BACKGROUND_LOCATION_DENIED: Background location access is denied. You first need to request background location permission.
Requesting Bluetooth permissions
To request Bluetooth access call requestBLEPermissions method of the plugin. This will result in displaying Bluetooth access permission dialog.
Future<String?> returnVal = geofencingFlutterPlugin.requestBLEPermissions();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Note: For beacon-fencing to work you will also need to request background location access. To request background location access please invoke requestPermissions method of the plugin with background parameter enabled.
Initializing the plugin
Plugin can be initialized by simply calling initialize method.
Map<String, String> woosmapSettings = {
"privateKeyWoosmapAPI": "<<WOOSMAP_KEY>>",
"trackingProfile": "liveTracking"
};
Future<String?> returnVal = geofencingFlutterPlugin.initialize(woosmapSettings);
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: $error');
});
Both configuration options privateKeyWoosmapAPI and trackingProfile are optional. You can also initialize the plugin by passing null configuration.
Future<String?> returnVal = geofencingFlutterPlugin.initialize();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: $error');
});
You can also set the Woosmap API key later by calling setWoosmapApiKey method.
Future<String?> returnVal = geofencingFlutterPlugin.setWoosmapApiKey("<<WOOSMAP_KEY>>");
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Tracking
Once you have initialized the plugin and the user has authorized location permissions, you can start tracking the user’s location.
To start tracking, call:
Future<String?> returnVal = geofencingFlutterPlugin.startTracking('liveTracking');
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
To stop tracking, call:
Future<String?> returnVal = geofencingFlutterPlugin.stopTracking();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Method startTracking accepts only following tracking profiles
- liveTracking
- passiveTracking
- visitsTracking
- beaconTracking
Tracking profile properties
| Property | liveTracking | passiveTracking | visitsTracking |
|---|---|---|---|
| trackingEnable | true | true | true |
| foregroundLocationServiceEnable | true | false | false |
| modeHighFrequencyLocation | true | false | false |
| visitEnable | false | false | true |
| classificationEnable | false | false | true |
| minDurationVisitDisplay | null | null | 300 |
| radiusDetectionClassifiedZOI | null | null | 50 |
| distanceDetectionThresholdVisits | null | null | 25 |
| currentLocationTimeFilter | 0 | 0 | 0 |
| currentLocationDistanceFilter | 0 | 0 | 0 |
| accuracyFilter | 100 | 100 | 100 |
| searchAPIEnable | false | true | false |
| searchAPICreationRegionEnable | false | true | false |
| searchAPITimeFilter | 0 | 0 | 0 |
| searchAPIDistanceFilter | 0 | 0 | 0 |
| distanceAPIEnable | false | false | false |
| modeDistance | null | null | null |
| outOfTimeDelay | 300 | 300 | 300 |
| DOUBLEOfDayDataDuration | 30 | 30 | 30 |
Listening to events
Location
To listen to location, call watchLocation method. After successful invokation of the method you'll need to call getWatchLocationStream method which will return a stream of Location object. Once you obtain the stream call the listen method of the stream.
Future<String?> returnVal = geofencingFlutterPlugin.watchLocation();
returnVal.then((value){
//Get the location stream
watchLocationStream = geofencingFlutterPlugin.getWatchLocationStream();
//Listen to the stream
watchLocationStream.listen((location){
//Location updates will be recieved here.
if (location != null) {
debugPrint(location.locationDescription);
} else {
debugPrint("Location is null");
}
});
}).catchError((error) {
debugPrint('An error occurred: $error');
});
To stop getting location updates:
Future<String?> returnVal = geofencingFlutterPlugin.clearLocationWatch();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: $error');
});
Define the radius value
When you create a Geofence around a POI (previously imported from Woosmap), manually define the radius value:
Future<String?> returnVal = _geofencingFlutterPlugin.setPoiRadius("100");
returnVal.then((value){
showToast(value!);
}).catchError((error) {
showToast('An error occurred: ${error.message}');
});
or choose the user_properties subfield that corresponds to radius value of the Geofence:
Future<String?> returnVal = _geofencingFlutterPlugin.setPoiRadius("radiusPOI");
returnVal.then((value){
showToast(value!);
}).catchError((error) {
showToast('An error occurred: ${error.message}');
});
Regions
Call watchRegions method to track Regions. Method will invoke a callback with Region object. Method will return a watch id which can be used later to remove the callback.
Future<String?> returnVal = geofencingFlutterPlugin.watchRegion();
returnVal.then((value){
//Get the region stream
watchRegionStream = geofencingFlutterPlugin.getWatchRegionStream();
//Listen to the stream
watchRegionStream.listen((region){
//Region updates will be recieved here.
if (location != null) {
debugPrint(region.eventName);
} else {
debugPrint("Region is null");
}
});
}).catchError((error) {
debugPrint('An error occurred: $error');
});
To remove watch:
Future<String?> returnVal = geofencingFlutterPlugin.clearRegionWatch();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: $error');
});
Background region events (application in the background or terminated)
getWatchRegionStream only delivers events while a Flutter engine of the application is running. To handle region events after the application has been terminated, register a Dart callback with registerBackgroundRegionCallback. The plugin starts a headless Flutter engine and invokes that callback in a background isolate.
The callback and the plugin entry point must survive tree shaking, so the callback has to be a top level or static function annotated with @pragma('vm:entry-point'):
import 'package:flutter/foundation.dart';
import 'package:geofencing_flutter_plugin/geofencing_flutter_plugin.dart';
@pragma('vm:entry-point')
void onBackgroundRegionEvent(Region region) {
// Runs in a background isolate, without the widget tree.
debugPrint('${region.eventName} on ${region.identifier}');
}
// Register it once, for instance right after initialize():
Future<bool> registered =
geofencingFlutterPlugin.registerBackgroundRegionCallback(onBackgroundRegionEvent);
To stop waking a background isolate:
Future<bool> removed = geofencingFlutterPlugin.unregisterBackgroundRegionCallback();
stopTracking() removes it as well: tracking is what produces the region events, so nothing is left that could wake a background isolate. Register the callback again after the next startTracking() call. To know whether one is currently registered, for instance to restore it at startup:
if (!await geofencingFlutterPlugin.hasBackgroundRegionCallback()) {
await geofencingFlutterPlugin.registerBackgroundRegionCallback(onBackgroundRegionEvent);
}
The registration is persisted natively, so hasBackgroundRegionCallback() stays accurate after the application has been restarted.
The callback receives the same Region object as the region stream, with the same fields on both platforms. It is invoked only when no Dart listener is consuming the region stream: while the application is running and listening with getWatchRegionStream, events keep going to the stream only, so an event is never handled twice.
Constraints of the background isolate
-
It shares no memory with the UI isolate: singletons, global variables, providers and anything holding state in the application are not available there. Only self contained logic transfers as is (pure functions, HTTP calls, local database, local notifications).
-
Plugins must be registered in that isolate. On Android this is automatic. On iOS, add the registrant to your
AppDelegate:import geofencing_flutter_plugin GeofencingFlutterPlugin.setPluginRegistrantCallback { registry in GeneratedPluginRegistrant.register(with: registry) } -
Keep the callback short. Both platforms run it on a limited background budget and long running work risks being terminated by the system.
-
On iOS the operating system relaunches the application for the region event: the plugin restarts the Woosmap service with the key and the tracking profile of the previous run, then runs the callback within a background task.
-
On Android the event is delivered by the Woosmap SDK broadcast, which starts the process when needed, for instance after the application was swiped away from the recent apps or its process was reclaimed by the system. An application that has been force stopped (Settings > Force stop, or
adb shell am force-stop) cannot be woken: Android cancels the geofence triggers of a force-stopped application and nothing is delivered until the user opens it again. Some manufacturers treat swiping the application away as a force stop when battery optimisation is enabled for it. Background location permission (GRANTED_BACKGROUND) is required on both platforms.
Initialize Salesforce MarketingCloud Connector
The SDK needs some input like credentials and object key to perform the API call to Salesforce Marketing Cloud API.
Input to initialize the SFMC connector
| Parameters | Description | Required |
|---|---|---|
| authenticationBaseURI | Authentication Base URI | Required |
| restBaseURI | REST Base URI | Required |
| client_id | client_id (journey_read and list_and_subscribers_read rights are required) | Required |
| client_secret | client_secret (journey_read and list_and_subscribers_read rights are required) | Required |
| contactKey | The ID that uniquely identifies a subscriber/contact | Required |
| regionEnteredEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_geofence_entered_event |
|
| regionExitedEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_geofence_exited_event |
|
| poiEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_POI_event |
|
| zoiClassifiedEnteredEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_zoi_classified_entered_event |
|
| zoiClassifiedExitedEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_zoi_classified_exited_event |
|
| visitEventDefinitionKey | Set the EventDefinitionKey that you want to use for the Woosmap event woos_Visit_event |
Initialize the connector implementation
Map<String, String> arguments = {
'authenticationBaseURI': config.authenticationBaseURI,
'restBaseURI':config.restBaseURI,
'client_id': config.clientId,
'client_secret': config.clientSecret,
'contactKey': config.contactKey,
'regionEnteredEventDefinitionKey': config.regionEnteredEventDefinitionKey,
'regionExitedEventDefinitionKey':config.regionExitedEventDefinitionKey,
};
Future<String?> returnVal = geofencingFlutterPlugin.setSFMCCredentials(arguments);
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Adding and removing regions
Call addRegion method to add a region that you want to monitor.
Region type can be circle or isochrone only.
Method will accept an object with the following attributes:
- regionId - Id of the region
- lat - Latitude
- lng - Longitude
- radius - Radius in meters
- type - type of region
Create a custom circle region
GeofenceRegion geofenceRegion = GeofenceRegio(
'7F91369E-467C-4CBD-8D41-6509815C4780',
51.50998,
-0.1337,
180,
RegionType.circle
);
Future<String?> returnVal = geofencingFlutterPlugin.addRegion(geofenceRegion);
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Create a custom isochrone region
GeofenceRegion geofenceRegion = GeofenceRegio(
'7F91369E-467C-4CBD-8D41-6509815C4780',
51.50998,
-0.1337,
180,
RegionType.isochrone
);
Future<String?> returnVal = geofencingFlutterPlugin.addRegion(geofenceRegion);
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Call removeRegions method to remove a region that you are monitoring. Method will accept the following parameter, and passing a null value will remove all the regions.
Future<String?> returnVal = geofencingFlutterPlugin.removeRegions('7F91369E-467C-4CBD-8D41-6509815C4780');
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Or To Delete all Regions
Future<String?> returnVal = geofencingFlutterPlugin.removeRegions();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Local database operations
- Get POIs: Call
getPoismethod to get an array of POIs from the local db
// Get single POI
Future<List<Poi>?> returnVal = geofencingFlutterPlugin.getPois(poiId);
returnVal.then((pois){
if (pois != null){
debugPrint('POIs: ${pois.length}');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
// Get all POIs
Future<List<Poi>?> returnVal = geofencingFlutterPlugin.getPois();
returnVal.then((pois){
if (pois != null){
debugPrint('POIs: ${pois.length}');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Delete POIs: Call
removePoismethod to clear all POIs from the local db.
Future<String?> returnVal = geofencingFlutterPlugin.removePois();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Refresh POIs: Call
refreshPoismethod to fetch updated POI information from Woosmap API.
Future<String?> returnVal = geofencingFlutterPlugin.refreshPois();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Get Locations: Call
getLocationsmethod to get an array of Locations from the local db.
//Get single location
Future<List<Location>?> returnVal = geofencingFlutterPlugin.getLocations(locationId);
returnVal.then((locations){
if (locations != null){
debugPrint('Locations: ${locations.length}');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
//Get all locations
Future<List<Location>?> returnVal = geofencingFlutterPlugin.getLocations();
returnVal.then((locations){
if (locations != null){
debugPrint('Locations: ${locations.length}');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Delete Locations: Call
removeLocationsmethod to clear all Locations info from the local db.
Future<String?> returnVal = geofencingFlutterPlugin.removeLocations();
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Get Regions: Call
getRegionsmethod to get an array of Regions from the local db. specify region id to retrieve specific region info
//Get single region
Future<List<Region>?> returnVal = geofencingFlutterPlugin.getRegions(regionId);
returnVal.then((regions){
if (regions != null){
debugPrint('Regions: ${regions.length}');
}else{
debugPrint('Regions is null');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
//Get all regions
Future<List<Region>?> returnVal = geofencingFlutterPlugin.getRegions();
returnVal.then((regions){
if (regions != null){
debugPrint('Regions: ${regions.length}');
}else{
debugPrint('Regions is null');
}
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
- Get Indoor Beacons: Call
getIndoorBeaconsmethod to get a list ofIndoorBeaconfrom the local db. You can pass avenueIdto get the beacons for a specific venue or you can passrefparameter which will get a beacon with specific identifier or you can pass both.
Future<List<IndoorBeacon>?> returnVal;
returnVal = geofencingFlutterPlugin.getIndoorBeacons(); //Get all indoor beacons
returnVal = geofencingFlutterPlugin.getIndoorBeacons(venueID: "YOUR-VENUE-ID"); //Get beacons for a venue
returnVal = geofencingFlutterPlugin.getIndoorBeacons(ref: "BEACON-IDENTIFIER"); //Get beacon with an identifier
returnVal = geofencingFlutterPlugin.getIndoorBeacons(venueID: "YOUR-VENUE-ID", ref: "BEACON-IDENTIFIER"); //Filter by both
returnVal.then((beacons) {
if (beacons != null) {
debugPrint('Indoor beacons: ${beacons.length}');
}
}).catchError((error) {
showToast('An error occurred: ${error.message}');
});
- Delete Indoor Beacons: Call
removeIndoorBeaconsmethod to delete all indoor beacons from the local DB.
Future<String?> returnVal = geofencingFlutterPlugin.removeIndoorBeacons();
returnVal.then((value) {
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Custom tracking profile
If preset tracking profiles don’t fit with your use cases, you can build your own profile and uses the startCustomTracking() method. There are two way to host the json file:
- Include json file in the client application (local) for ios.
- For local mode put json file in assets folder in android.
- Host externally in a file folder in your information system (external)
Future<String?> returnVal = geofencingFlutterPlugin.startCustomTracking(ProfileSource.local, 'localProfile.json');
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
or
Future<String?> returnVal = geofencingFlutterPlugin.startCustomTracking(ProfileSource.external, 'https://raw.githubusercontent.com/lpernelle-wgs/files/master/customProfileLeo.json');
returnVal.then((value){
debugPrint(value!);
}).catchError((error) {
debugPrint('An error occurred: ${error.message}');
});
Build a custom tracking profile
Define tracking properties in a Json file that respect the Json Schema in the Tracking properties page.
License
BSD-3