maui-geolocation
SKILL.md
.NET MAUI Geolocation
Platform permissions
Android
Add to Platforms/Android/AndroidManifest.xml inside <manifest>:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 10+ background location (only if needed) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
iOS
Add to Platforms/iOS/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location to provide nearby results.</string>
For full-accuracy prompts on iOS 14+, also add:
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>FullAccuracyUsageKey</key>
<string>This app needs precise location for turn-by-turn directions.</string>
</dict>
macOS (Mac Catalyst)
Add to Platforms/MacCatalyst/Entitlements.plist:
<key>com.apple.security.personal-information.location</key>
<true/>
Windows
No manifest changes required. Location capability is enabled by default.
Core API — Geolocation.Default
| Method | Returns | Purpose |
|---|---|---|
GetLastKnownLocationAsync() |
Location? |
Cached device location (fast, may be stale) |
GetLocationAsync(GeolocationRequest, CancellationToken) |
Location? |
Fresh GPS fix with desired accuracy |
StartListeningForegroundAsync(GeolocationListeningRequest) |
bool |
Begin continuous location updates |
StopListeningForeground() |
void |
Stop continuous updates |
One-shot location
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var location = await Geolocation.Default.GetLocationAsync(request, cts.Token);
if (location is null)
{
// Location unavailable — GPS off, permissions denied, or timeout
return;
}
Console.WriteLine($"{location.Latitude}, {location.Longitude} ±{location.Accuracy}m");
}
catch (FeatureNotSupportedException)
{
// Device lacks GPS hardware
}
catch (PermissionException)
{
// Location permission not granted
}
Always check for null — the method returns null when the device cannot obtain a fix.
Continuous listening
public partial class TrackingViewModel : ObservableObject
{
[ObservableProperty] Location? currentLocation;
public async Task StartTracking()
{
Geolocation.Default.LocationChanged += OnLocationChanged;
var request = new GeolocationListeningRequest(GeolocationAccuracy.High, TimeSpan.FromSeconds(5));
var success = await Geolocation.Default.StartListeningForegroundAsync(request);
if (!success)
Geolocation.Default.LocationChanged -= OnLocationChanged;
}
public void StopTracking()
{
Geolocation.Default.StopListeningForeground();
Geolocation.Default.LocationChanged -= OnLocationChanged;
}
void OnLocationChanged(object? sender, GeolocationLocationChangedEventArgs e)
{
CurrentLocation = e.Location;
}
}
GeolocationAccuracy levels
| Enum value | Android (m) | iOS (m) | Windows (m) |
|---|---|---|---|
Lowest |
500 | 3000 | 1000–5000 |
Low |
500 | 1000 | 300–3000 |
Medium |
100–500 | 100 | 30–500 |
High |
0–100 | 10 | ≤30 |
Best |
0–100 | ~0 | ≤10 |
Higher accuracy consumes more battery. Use the lowest level that satisfies your feature.
CancellationToken pattern
Always pass a CancellationToken to GetLocationAsync to avoid indefinite hangs:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var location = await Geolocation.Default.GetLocationAsync(
new GeolocationRequest(GeolocationAccuracy.High), cts.Token);
DI-friendly service wrapper
Register IGeolocation in MauiProgram.cs:
builder.Services.AddSingleton<IGeolocation>(Geolocation.Default);
builder.Services.AddSingleton<LocationService>();
Consume via constructor injection:
public class LocationService(IGeolocation geolocation)
{
public async Task<Location?> GetCurrentAsync(CancellationToken ct = default)
{
var cached = await geolocation.GetLastKnownLocationAsync();
if (cached is not null && cached.Timestamp > DateTimeOffset.UtcNow.AddMinutes(-5))
return cached;
return await geolocation.GetLocationAsync(
new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10)), ct);
}
}
Platform gotchas
- iOS 14 reduced accuracy: Users can grant "approximate" location. Check
location.Accuracy— values > 100 m likely indicate reduced precision. UseGeolocationRequest.RequestFullAccuracywith a matching key fromNSLocationTemporaryUsageDescriptionDictionaryto prompt for full accuracy. - Mock locations (
IsFromMockProvider): On Android,location.IsFromMockProvideristruewhen a mock-location app is active. Always check this in security-sensitive flows. - Altitude 0.0 on Android: Some Android devices return
0.0forAltitudewhen GPS has no barometric sensor. Treat0.0as "unknown" rather than sea level. - Null returns:
GetLastKnownLocationAsyncreturnsnullon first boot or after a location-data reset. Always fall back toGetLocationAsync. - Permissions at runtime: Call
Permissions.RequestAsync<Permissions.LocationWhenInUse>()before any geolocation call, or handlePermissionException. - Background location on Android 10+:
ACCESS_BACKGROUND_LOCATIONmust be requested separately from foreground permissions and triggers a distinct system dialog.
Weekly Installs
6
Repository
davidortinau/maui-skillsGitHub Stars
71
First Seen
13 days ago
Security Audits
Installed on
opencode6
github-copilot6
codex6
kimi-cli6
gemini-cli6
amp6