Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

Geofencing

GitHub GitHub stars for shinyorg/shiny
Downloads NuGet downloads for Shiny.Locations
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.

Feature Android iOS Windows
Max Regions 60 20 Unlimited
Background Support Yes Yes Yes
Google Play Fallback GPS-direct when unavailable N/A N/A
Changed files
  • MyApp/
Delegates/MyGeofenceDelegate.cs
MyGeofenceDelegate.cs
1using Shiny.Locations;23namespace MyApp.Delegates;45public partial class MyGeofenceDelegate : IGeofenceDelegate6{7    public MyGeofenceDelegate()8    {9    }1011    public Task OnStatusChanged(GeofenceState newStatus, GeofenceRegion region)12    {13        throw new NotImplementedException();14    }15}1617#if ANDROID18public partial class MyGeofenceDelegate : IAndroidForegroundServiceDelegate19{20    public void Configure(AndroidX.Core.App.NotificationCompat.Builder builder)21    {22        23    }24}25#endif

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();

If you re-register the same regions on every launch, use TryStartMonitoring to avoid duplicates. It only starts monitoring when a region with that identifier isn’t already monitored, and — when replaceIfExists is true (the default) — stops and restarts an existing region so changed position or notification settings take effect. It returns true if the region already existed, false if it was newly added.

var region = new GeofenceRegion(
"work",
new Position(43.6532, -79.3832),
Distance.FromMeters(200)
);
// Idempotent: safe to call repeatedly without duplicating or throwing
bool alreadyExisted = await geofenceManager.TryStartMonitoring(region);
// Add without replacing an existing region of the same identifier
await geofenceManager.TryStartMonitoring(region, replaceIfExists: false);
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
);
Parameter Type Default Description
Identifier string required Unique ID for the region
Center Position required Latitude/longitude of the circle center
Radius Distance required Radius of the circular geofence
SingleUse bool false Automatically stop monitoring after first trigger
NotifyOnEntry bool true Fire delegate when entering the region
NotifyOnExit bool true Fire 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.

claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
View shiny-client Plugin