AgentSkillsCN

maui-geolocation

借助 Microsoft.Maui.Devices.Sensors 为 .NET MAUI 应用添加地理位置功能。涵盖一次性定位与连续定位、平台权限(Android、iOS、macOS、Windows)、精度等级、CancellationToken 的使用、模拟位置检测,以及适配 DI 的服务封装。适用于在 MAUI 应用中实现 GPS/定位功能、请求定位权限,或排查 MAUI 应用中平台特定的地理位置行为时使用。

SKILL.md
--- frontmatter
name: maui-geolocation
description: >
  Add geolocation capabilities to .NET MAUI apps using Microsoft.Maui.Devices.Sensors.
  Covers one-shot and continuous location, platform permissions (Android, iOS, macOS, Windows),
  accuracy levels, CancellationToken usage, mock-location detection, and a DI-friendly service wrapper.
  Use this skill when implementing GPS/location features, requesting location permissions,
  or troubleshooting platform-specific geolocation behavior in MAUI applications.

.NET MAUI Geolocation

Platform permissions

Android

Add to Platforms/Android/AndroidManifest.xml inside <manifest>:

xml
<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:

xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location to provide nearby results.</string>

For full-accuracy prompts on iOS 14+, also add:

xml
<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:

xml
<key>com.apple.security.personal-information.location</key>
<true/>

Windows

No manifest changes required. Location capability is enabled by default.

Core API — Geolocation.Default

MethodReturnsPurpose
GetLastKnownLocationAsync()Location?Cached device location (fast, may be stale)
GetLocationAsync(GeolocationRequest, CancellationToken)Location?Fresh GPS fix with desired accuracy
StartListeningForegroundAsync(GeolocationListeningRequest)boolBegin continuous location updates
StopListeningForeground()voidStop continuous updates

One-shot location

csharp
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

csharp
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 valueAndroid (m)iOS (m)Windows (m)
Lowest50030001000–5000
Low5001000300–3000
Medium100–50010030–500
High0–10010≤30
Best0–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:

csharp
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:

csharp
builder.Services.AddSingleton<IGeolocation>(Geolocation.Default);
builder.Services.AddSingleton<LocationService>();

Consume via constructor injection:

csharp
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. Use GeolocationRequest.RequestFullAccuracy with a matching key from NSLocationTemporaryUsageDescriptionDictionary to prompt for full accuracy.
  • Mock locations (IsFromMockProvider): On Android, location.IsFromMockProvider is true when a mock-location app is active. Always check this in security-sensitive flows.
  • Altitude 0.0 on Android: Some Android devices return 0.0 for Altitude when GPS has no barometric sensor. Treat 0.0 as "unknown" rather than sea level.
  • Null returns: GetLastKnownLocationAsync returns null on first boot or after a location-data reset. Always fall back to GetLocationAsync.
  • Permissions at runtime: Call Permissions.RequestAsync<Permissions.LocationWhenInUse>() before any geolocation call, or handle PermissionException.
  • Background location on Android 10+: ACCESS_BACKGROUND_LOCATION must be requested separately from foreground permissions and triggers a distinct system dialog.