Geolocation and GPS Tracking in .NET MAUI 10: Background Location, Geofencing, and Battery

Build GPS tracking and geofencing in .NET MAUI 10 with battery-friendly background services on Android and iOS. Working code, permissions, and tips.

GPS Tracking in .NET MAUI 10 (2026 Guide)

Updated: May 26, 2026

Geolocation in .NET MAUI 10 lets you read a device's current GPS position, listen to continuous location updates, and run background location tracking on both Android and iOS through the cross-platform IGeolocation interface, plus a small amount of platform-specific code for foreground services and geofencing. This guide walks through one-shot lookups, streaming updates, background tracking with foreground services and BGTaskScheduler, geofencing patterns, battery-efficient accuracy tuning, and the permission strings you need on each platform. Everything here targets .NET MAUI 10, Android 16, and iOS 19.

Honestly, I lost a weekend last year chasing a "GetLocationAsync returns null" bug that turned out to be a missing manifest entry. So if you're hitting weird null fixes or Play Store rejections, jump straight to the permissions section.

  • Geolocation.Default.GetLocationAsync returns a single fix; GetLastKnownLocationAsync is faster and battery-friendly when staleness is acceptable.
  • Continuous tracking on Android needs a foreground service with the FOREGROUND_SERVICE_LOCATION permission introduced in Android 14+.
  • iOS background updates require NSLocationAlwaysAndWhenInUseUsageDescription, the location background mode, and AllowsBackgroundLocationUpdates = true on CLLocationManager.
  • True geofencing isn't in the MAUI Essentials API. Wrap GeofencingClient on Android and CLLocationManager.StartMonitoring on iOS via partial classes.
  • Switching GeolocationAccuracy.Best to Medium can cut GPS battery use by 60 to 80% on long-running trackers.
  • Android 16 enforces stricter background-location prompts: request ACCESS_BACKGROUND_LOCATION separately, only after foreground permission is granted.

Setup and platform permissions

The cross-platform geolocation API lives in Microsoft.Maui.Devices.Sensors and is wired up automatically when you call MauiApp.CreateBuilder().UseMauiApp<App>(). No NuGet package is required for foreground lookups, but every platform needs explicit manifest entries before the OS will hand you a coordinate. Skipping this step is the single most common reason GetLocationAsync returns null or throws PermissionException on first run.

On Android, edit Platforms/Android/AndroidManifest.xml and add the permissions matching your use case. For one-shot foreground lookups you'll need ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION. For background tracking you also need ACCESS_BACKGROUND_LOCATION and, since Android 14, FOREGROUND_SERVICE plus FOREGROUND_SERVICE_LOCATION.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />

On iOS, open Platforms/iOS/Info.plist and add usage description strings. Apple will reject the build without them, and the OS will silently deny the permission at runtime if they're missing.

<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to show nearby places on the map.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Background location lets us continue your run when the screen is off.</string>
<key>UIBackgroundModes</key>
<array><string>location</string></array>

Both platforms require a runtime permission request. Use Permissions.RequestAsync<Permissions.LocationWhenInUse>() from .NET MAUI Essentials for the foreground prompt, and request Permissions.LocationAlways only after the user has granted the foreground variant. Android 16 enforces this two-step prompt strictly. Combining them into one dialog will fail silently, which is fun to debug.

How do you get the current location in .NET MAUI?

The simplest way to get the current location in .NET MAUI is to call Geolocation.Default.GetLocationAsync with a GeolocationRequest describing the desired accuracy and timeout. The method returns a Location object with latitude, longitude, altitude, accuracy radius, and a timestamp.

Always wrap the call in a try/catch. The API throws FeatureNotSupportedException on emulators without GPS, FeatureNotEnabledException when location services are off, and PermissionException when the user denies the prompt. I've seen each of these crash production apps because someone forgot one branch.

public async Task<Location?> GetCurrentLocationAsync()
{
    var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
    if (status != PermissionStatus.Granted)
        return null;

    try
    {
        var request = new GeolocationRequest(
            GeolocationAccuracy.Medium,
            TimeSpan.FromSeconds(10));

        // CancellationToken lets you abort if the user moves away
        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
        return await Geolocation.Default.GetLocationAsync(request, cts.Token);
    }
    catch (FeatureNotEnabledException)
    {
        await Shell.Current.DisplayAlert("GPS off", "Enable location services.", "OK");
        return null;
    }
    catch (PermissionException)
    {
        return null;
    }
}

For battery-sensitive screens that only need a rough position (showing a nearest-store list, for example), call GetLastKnownLocationAsync first. It returns a cached fix held by the OS without powering the radio, typically in under 50 ms versus 2 to 5 seconds for a fresh GPS lock. Fall back to GetLocationAsync only if the cached result is null or older than your staleness budget (DateTimeOffset.UtcNow - location.Timestamp > TimeSpan.FromMinutes(5)).

Listening to continuous location updates

For a maps screen that pans as the user moves, subscribe to Geolocation.Default.LocationChanged and start streaming with StartListeningForegroundAsync. This API replaced the manual polling loop required in Xamarin.Forms and works on Android, iOS, and Windows in MAUI 10. The listener stays alive as long as the app is in the foreground; lock the screen or switch apps and it stops automatically. The next section covers background patterns.

public async Task StartLiveTrackingAsync()
{
    Geolocation.Default.LocationChanged += OnLocationChanged;
    Geolocation.Default.ListeningFailed += OnListeningFailed;

    var request = new GeolocationListeningRequest(
        GeolocationAccuracy.Best,
        minimumTime: TimeSpan.FromSeconds(2));

    bool started = await Geolocation.Default.StartListeningForegroundAsync(request);
    if (!started)
        await Shell.Current.DisplayAlert("Error", "Could not start listening", "OK");
}

void OnLocationChanged(object? sender, GeolocationLocationChangedEventArgs e)
{
    // Marshal to the UI thread before updating bound properties
    MainThread.BeginInvokeOnMainThread(() =>
    {
        CurrentLat = e.Location.Latitude;
        CurrentLng = e.Location.Longitude;
        SpeedMps = e.Location.Speed ?? 0;
    });
}

public void StopLiveTracking()
{
    Geolocation.Default.StopListeningForeground();
    Geolocation.Default.LocationChanged -= OnLocationChanged;
}

The minimumTime parameter is a hint, not a contract. Android may deliver updates faster if other apps are also subscribed, and iOS will throttle when the device is stationary. Always debounce on top of the OS throttle if you persist each fix to a database. For an in-depth look at piping these updates into a map control, see our guide to integrating Google Maps, Apple Maps, and Mapbox in .NET MAUI 10.

Background location tracking on Android

To track location on Android while your app is backgrounded or the screen is locked, you must run a foreground service. That's a special service type that shows a persistent notification and is exempt from Doze mode. As of Android 14 (API 34), the service must declare the location type, and as of Android 16 the user-facing notification cannot be dismissed while tracking is active. Implement the service in Platforms/Android:

[Service(ForegroundServiceType = ForegroundService.TypeLocation)]
public class LocationTrackerService : Service
{
    const int NotifId = 4242;
    const string ChannelId = "location_tracker";

    public override StartCommandResult OnStartCommand(
        Intent? intent, StartCommandFlags flags, int startId)
    {
        CreateChannelIfNeeded();
        var notif = new NotificationCompat.Builder(this, ChannelId)
            .SetContentTitle("Tracking your route")
            .SetContentText("Tap to return to the app")
            .SetSmallIcon(Resource.Drawable.ic_tracker)
            .SetOngoing(true)
            .Build();

        StartForeground(NotifId, notif,
            ForegroundService.TypeLocation);

        // Hand off to MAUI's IGeolocation. It now keeps firing
        // LocationChanged because the process is foreground-priority.
        return StartCommandResult.Sticky;
    }

    public override IBinder? OnBind(Intent? intent) => null;
}

Start the service from your shared code via a dependency-injected interface that resolves to ContextCompat.StartForegroundService(Platform.AppContext, intent) on Android. Stop it with StopService(intent) when the user ends their trip. For broader patterns on long-running work (WorkManager versus foreground services, plus the iOS equivalents), read our deep dive on background tasks in .NET MAUI.

Background location tracking on iOS

iOS handles background location entirely through CLLocationManager. Set AllowsBackgroundLocationUpdates = true, enable the location background mode in Info.plist, and request the Always authorization level. The system shows a small blue indicator at the top of the screen while updates flow.

For step counting and walking-route apps where battery matters more than precision, prefer StartMonitoringSignificantLocationChanges. It relies on cell-tower changes and consumes almost no power, but only fires every 500 m or so.

// In a partial class under Platforms/iOS
public partial class IosLocationTracker
{
    CLLocationManager? _mgr;

    public void Start()
    {
        _mgr = new CLLocationManager
        {
            DesiredAccuracy = CLLocation.AccuracyNearestTenMeters,
            DistanceFilter = 10, // meters
            AllowsBackgroundLocationUpdates = true,
            PausesLocationUpdatesAutomatically = false,
            ShowsBackgroundLocationIndicator = true
        };
        _mgr.LocationsUpdated += (s, e) =>
        {
            foreach (var loc in e.Locations)
                MessagingCenter.Send(this, "Loc", loc);
        };
        _mgr.RequestAlwaysAuthorization();
        _mgr.StartUpdatingLocation();
    }
}

For periodic background fetches that don't need streaming (a once-per-hour location upload, say), register a BGAppRefreshTask via BGTaskScheduler. iOS schedules the task opportunistically based on user behavior; you can't guarantee a fixed interval. The official Core Location documentation covers the full CLLocationManager surface area.

Does .NET MAUI support geofencing?

.NET MAUI 10 does not include a cross-platform geofencing abstraction in the Essentials APIs, but you can build one in about a hundred lines by wrapping each platform's native client behind a shared interface. Geofencing means asking the OS to wake your app when the device crosses a circular boundary defined by a center coordinate and a radius. Unlike polling, it costs almost no battery because the OS reuses cell-tower events it's already processing.

On Android, use LocationServices.GetGeofencingClient(context) from Google Play Services. Build Geofence objects with SetCircularRegion(lat, lng, radius) and SetTransitionTypes(Geofence.GeofenceTransitionEnter | Geofence.GeofenceTransitionExit), wrap them in a GeofencingRequest, and submit with AddGeofences and a PendingIntent pointing to a BroadcastReceiver. Android limits each app to 100 geofences and silently drops events when the user disables high-accuracy location.

On iOS, the equivalent is CLLocationManager.StartMonitoring(new CLCircularRegion(...)). The system delivers RegionEntered and RegionLeft callbacks even when your app is terminated, but limits you to 20 regions per app. Most production apps work around this by maintaining a server-side list and re-registering the closest 20 each time the device moves significantly.

// Shared interface registered in MauiProgram.cs
public interface IGeofenceService
{
    Task AddAsync(string id, double lat, double lng, double radiusMeters);
    Task RemoveAsync(string id);
    event EventHandler<GeofenceEvent> Crossed;
}

// Usage from a ViewModel
await _geofence.AddAsync(
    id: "store-1138",
    lat: 47.6062, lng: -122.3321,
    radiusMeters: 150);

Battery optimization and accuracy tuning

GPS is the single largest battery sink on a phone. A continuously listening tracker at GeolocationAccuracy.Best can drain a fully charged device in 4 to 6 hours. Tune four knobs to keep usage reasonable: accuracy level, update frequency, distance filter, and platform-appropriate motion APIs. The Microsoft .NET MAUI geolocation documentation maps each GeolocationAccuracy value to native equivalents and battery class.

AccuracyTypical errorAndroid sourceiOS sourceBattery cost
Lowest~ 1000 mCell networkkCLLocationAccuracyThreeKilometersNegligible
Low~ 500 mWi-Fi triangulationkCLLocationAccuracyKilometerLow
Medium30 to 500 mFused (Wi-Fi + Cell)kCLLocationAccuracyHundredMetersModerate
High< 30 mFused with GPSkCLLocationAccuracyNearestTenMetersHigh
Best< 5 mRaw GPSkCLLocationAccuracyBestVery high

For ride-sharing or fitness use cases, start at Best and downshift to Medium after the user has been stationary for 30 seconds. Use the platform pedometer APIs (StepCounter on Android, CMPedometer on iOS) to detect movement without burning the GPS. On iOS, set PausesLocationUpdatesAutomatically = true so the OS can shut down updates when the device hasn't moved for a few minutes. It resumes automatically when motion is detected.

Also avoid running expensive work inside the LocationChanged handler. Persisting each fix to SQLite, reverse-geocoding, and re-rendering a polyline on every update will keep the CPU awake even between GPS pulses. Batch writes with a queue and flush every 30 seconds. For tips on profiling these patterns, see mastering .NET MAUI performance.

Testing GPS with mock locations

Emulators ship with mock-location support that lets you script routes without leaving your desk. In Android Studio's emulator, open Extended Controls → Location and either drop pins on the map or import a GPX file to play back a recorded route. In Xcode's simulator, use Features → Location to pick a preset (City Bicycle Ride, Freeway Drive) or a custom GPX file added to your scheme. On physical Android devices you can enable Developer Options → Mock Location App and use FakeGPS or ADB commands (adb emu geo fix <lng> <lat>).

For automated tests, abstract IGeolocation behind your own service interface and inject a fake that yields a scripted IAsyncEnumerable<Location>. This keeps your unit tests deterministic and lets you assert that your geofencing logic fires entry/exit events at the right coordinates. It's far more reliable than driving an emulator from a test runner. The .NET MAUI GitHub repository includes sample geolocation tests under src/Essentials/test that follow this pattern.

Frequently Asked Questions

How accurate is .NET MAUI geolocation?

Accuracy depends on the GeolocationAccuracy level and the device's available sensors. Outdoors with a clear sky and GeolocationAccuracy.Best, expect 3 to 10 meters. Indoors or in urban canyons, accuracy degrades to 30 to 100 meters even at the highest setting because the device falls back to Wi-Fi and cell triangulation.

Why does GetLocationAsync return null in .NET MAUI?

The most common causes are a denied permission, location services turned off at the OS level, an emulator without a configured mock location, or a timeout that fired before the GPS got a fix. Wrap the call in try/catch for PermissionException, FeatureNotEnabledException, and FeatureNotSupportedException, and bump the request timeout to at least 15 seconds for the first fix.

How do you reduce battery drain when tracking GPS continuously?

Use the lowest accuracy that meets your use case, increase DistanceFilter so the OS only delivers updates after the device has moved, pause updates when the user is stationary (detected via pedometer APIs), and batch writes instead of processing every fix. Switching from Best to Medium typically cuts GPS battery use by 60 to 80%.

Does .NET MAUI work with Google Play Services Fused Location Provider?

Yes. When Google Play Services is installed, the Android implementation of IGeolocation delegates to the Fused Location Provider, which blends GPS, Wi-Fi, cell, and sensor data. On devices without Play Services (Huawei or AOSP builds, for example) it falls back to LocationManager directly.

Can I track location when the app is closed in .NET MAUI?

Yes, but only with platform-specific code. On Android, a foreground service keeps tracking after the user moves away from the app; if the user swipes the app from the recents list, the service continues until you stop it. On iOS, significant-change monitoring and registered geofences fire callbacks even after the app is terminated by the system, but standard StartUpdatingLocation does not survive a full kill.

Article changelog (1)
  • — Expanded with TL;DR, table of contents, or additional sections
About the Author Marcus Okonkwo

Marcus came into .NET via a winding path: six years writing Android in Kotlin at a London ad-tech firm, two years on a React Native team at a payments company, then a switch to .NET MAUI in 2022 when his current employer (a property-management SaaS in Manchester) consolidated their mobile stack onto C#. He now leads a team of five mobile engineers and owns the MAUI app end to end. He tends to write the comparison pieces other people avoid: MAUI versus Flutter on real hardware, Shell navigation versus a hand-rolled stack, CommunityToolkit.Mvvm versus ReactiveUI for new projects, and what actually breaks when you move from MAUI 8 to 9. He has the .NET MAUI MVP designation as of 2025 and contributes intermittently to the CommunityToolkit.Maui repo.