GPS
Getting Started
Section titled “Getting Started”| GitHub | |
| Downloads | |
| Blazor |
Shiny makes realtime background location updates easy on MAUI. A Blazor WebAssembly
implementation is also available via Shiny.Locations.Blazor for foreground GPS in
the browser.
Platform Notes
Section titled “Platform Notes”| Feature | iOS | Android | Windows | Blazor (Web) |
|---|---|---|---|---|
| Foreground GPS | Full | Full | Full | Full |
| Background GPS | Full | Full (foreground service) | Not Supported | Not Supported |
| Geofencing | Full | Full | Not Supported | Not Supported |
Blazor WebAssembly Setup
Section titled “Blazor WebAssembly Setup”Install Shiny.Locations.Blazor and register it in your Program.cs:
using Shiny;
var builder = WebAssemblyHostBuilder.CreateDefault(args);// ...builder.Services.AddGps();// or with a (foreground-only) delegate:builder.Services.AddGps<MyGpsDelegate>();Then inject IGpsManager into your component just like on MAUI:
@inject IGpsManager GpsService
@code { protected override async Task OnInitializedAsync() { var status = await GpsService.RequestAccess(GpsRequest.Foreground); if (status == AccessState.Available) await GpsService.StartListener(GpsRequest.Foreground); }}The first call to RequestAccess will trigger the browser’s permission prompt.
A working sample lives at samples/Sample.Blazor/Pages/Gps.razor.
Starting/Stopping the GPS Service
Section titled “Starting/Stopping the GPS Service”IGpsManager gpsManager; // injected, resolved, etcawait gpsManager.StartListener(new GpsRequest{ UseBackground = true});
gpsManager.StopListening();Observing in the background
Section titled “Observing in the background”First, create the delegate that implements IGpsDelegate
public partial class MyGpsDelegate : Shiny.Locations.IGpsDelegate{ public MyGpsDelegate() { // like all other shiny delegates, dependency injection works here // treat this as a singleton }
public Task OnReading(IGpsReading reading) { // do something with the reading }}Controlling GPS Notification on Android
Section titled “Controlling GPS Notification on Android”#if ANDROIDpublic partial class MyGpsDelegate : Shiny.IAndroidForegroundServiceDelegate{ public void ConfigureNotification(AndroidX.Core.App.NotificationCompat.Builder builder) { builder .SetContentTitle("MyApp") .SetContentText("My App is following you!! images") .SetSmallIcon(Resource.Mipmap.youricon); }}#endifNext, let’s register that with your app builder/hosting/service collection
services.UseGps<MyGpsDelegate>();Lastly, last start it up
IGpsManager gpsManager; // injected, resolved, etcawait gpsManager.StartListener(new GpsRequest{ UseBackground = true});Stationary Detection
Section titled “Stationary Detection”All GPS providers automatically detect when the user is stationary and set GpsReading.IsStationary on each reading before it reaches your delegate or observable stream.
- iOS 18+ uses native
CLLocationUpdaterstationary detection provided by the OS. - iOS legacy and Android use a distance + time threshold algorithm: if the user moves less than a configurable number of meters within a configurable number of seconds, they are marked stationary.
The default thresholds are 10 meters and 30 seconds. You can configure these via the platform-specific request types:
#if ANDROIDvar request = new AndroidGpsRequest( BackgroundMode: GpsBackgroundMode.Realtime, StationaryMetersThreshold: 15, StationarySecondsThreshold: 60);#elif IOSvar request = new AppleGpsRequest( BackgroundMode: GpsBackgroundMode.Realtime, StationaryMetersThreshold: 15, StationarySecondsThreshold: 60);#endifThen check the reading in your delegate or observable:
gpsManager.WhenReading().Subscribe(reading =>{ if (reading.IsStationary) { // user is stationary }});Easier Delegate
Section titled “Easier Delegate”The platform mechanics don’t always play by the rules for time & distance filters that you may expect especially with a more ‘real time’ setup.
To work around these issues, you can use the GpsDelegate class which will handle the filtering for you.
Minimum Filters (AND)
Section titled “Minimum Filters (AND)”When both MinimumDistance and MinimumTime are set, both conditions must be met before OnGpsReading fires. If only one is set, that single condition is used.
public class MyGpsDelegate : GpsDelegate{ public MyGpsDelegate(ILogger<MyGpsDelegate> logger) : base(logger) { // BOTH of these must be satisfied for OnGpsReading to fire this.MinimumDistance = Distance.FromMeters(200); this.MinimumTime = TimeSpan.FromMinutes(1); }
protected override async Task OnGpsReading(GpsReading reading) {
}}Maximum Filters (OR, overrides minimums)
Section titled “Maximum Filters (OR, overrides minimums)”MaximumDistance and MaximumTime act as safety nets. If either maximum threshold is crossed, OnGpsReading fires immediately regardless of whether the minimum conditions are met. This ensures readings are never suppressed for too long.
public class MyGpsDelegate : GpsDelegate{ public MyGpsDelegate(ILogger<MyGpsDelegate> logger) : base(logger) { // Both minimums must be met (AND) this.MinimumDistance = Distance.FromMeters(200); this.MinimumTime = TimeSpan.FromMinutes(1);
// Either maximum crossed will always fire (OR), overriding minimums this.MaximumDistance = Distance.FromKilometers(2); this.MaximumTime = TimeSpan.FromMinutes(10); }
protected override async Task OnGpsReading(GpsReading reading) {
}}AI Coding Assistant
Section titled “AI Coding Assistant”Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skills Step 2 — Install the plugin:
claude plugin install shiny-client@shiny Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skills Step 2 — Install the plugin:
copilot plugin install shiny-client@shiny