Skip to content

Geofencing

Operating Systems
Android
iOS
Windows

Geofencing lets your app react when a user enters or exits a geographic area — even when the app is not running. Unlike GPS tracking, geofencing is managed by the operating system and is highly battery efficient, making it ideal for location-triggered background tasks.

FeatureAndroidiOSWindows
Max Regions6020Unlimited
Background SupportYesYesYes
Google Play FallbackGPS-direct when unavailableN/AN/A
Shiny.LocationsNuGet package Shiny.Locations
Shiny.Hosting.MauiNuGet package Shiny.Hosting.Maui

Implement IGeofenceDelegate to handle geofence enter/exit events. This delegate fires in the background when a boundary is crossed.

public class MyGeofenceDelegate : IGeofenceDelegate
{
readonly ILogger<MyGeofenceDelegate> logger;
// Full dependency injection support - treat as a singleton
public MyGeofenceDelegate(ILogger<MyGeofenceDelegate> logger)
{
this.logger = logger;
}
public async Task OnStatusChanged(GeofenceState newStatus, GeofenceRegion region)
{
if (newStatus == GeofenceState.Entered)
{
this.logger.LogInformation("Entered region: {Region}", region.Identifier);
// send a notification, sync data, trigger an action, etc.
}
else if (newStatus == GeofenceState.Exited)
{
this.logger.LogInformation("Exited region: {Region}", region.Identifier);
}
}
}

Inject IGeofenceManager to manage geofence regions at runtime.

Always request access before starting monitoring:

IGeofenceManager geofenceManager; // injected
var access = await geofenceManager.RequestAccess();
if (access != AccessState.Available)
{
// Handle denied/restricted - show user messaging
return;
}
// Start monitoring a region
await geofenceManager.StartMonitoring(new GeofenceRegion(
"work",
new Position(43.6532, -79.3832), // latitude, longitude
Distance.FromMeters(200) // radius
));
// Stop monitoring a specific region
await geofenceManager.StopMonitoring("work");
// Stop monitoring all regions
await geofenceManager.StopAllMonitoring();
var region = new GeofenceRegion(
Identifier: "coffee-shop",
Center: new Position(43.6532, -79.3832),
Radius: Distance.FromMeters(100),
SingleUse: false, // true = auto-remove after first trigger
NotifyOnEntry: true, // fire delegate on enter
NotifyOnExit: true // fire delegate on exit
);
ParameterTypeDefaultDescription
IdentifierstringrequiredUnique ID for the region
CenterPositionrequiredLatitude/longitude of the circle center
RadiusDistancerequiredRadius of the circular geofence
SingleUseboolfalseAutomatically stop monitoring after first trigger
NotifyOnEntrybooltrueFire delegate when entering the region
NotifyOnExitbooltrueFire delegate when exiting the region
// Get all currently monitored regions
var regions = geofenceManager.GetMonitorRegions();
// Request the current state of a region (inside or outside)
var state = await geofenceManager.RequestState(region);
// GeofenceState.Entered, GeofenceState.Exited, or GeofenceState.Unknown
// Check current permission status without prompting
var status = geofenceManager.CurrentStatus;

By default, AddGeofencing uses the native OS geofencing APIs. On Android, if Google Play Services is unavailable, Shiny automatically falls back to GPS-direct geofencing.

You can also explicitly opt into GPS-direct mode:

services.AddGpsDirectGeofencing<MyGeofenceDelegate>();

Shiny handles all the platform complexity behind a single API:

  • Android: Uses Google Play Services geofencing when available, falls back to GPS-direct otherwise
  • iOS 18+: Uses the modern CLMonitor API
  • iOS < 18: Uses the legacy CLLocationManager region monitoring API
  • Windows: Uses Windows.Devices.Geolocation.Geofencing

Monitored regions are automatically persisted and restored on app restart — you don’t need to re-register them.