Skip to content

Add UserLocationChanged event and LastUserLocation property to Map#33799

Merged
jfversluis merged 4 commits intonet11.0from
feature/map-user-location
Mar 2, 2026
Merged

Add UserLocationChanged event and LastUserLocation property to Map#33799
jfversluis merged 4 commits intonet11.0from
feature/map-user-location

Conversation

@jfversluis
Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

This PR adds the ability to receive user location updates from the Map control when IsShowingUser is enabled.

New Public API

  • Map.LastUserLocation - Read-only property that returns the most recent user location, or null if not yet available
  • Map.UserLocationChanged - Event fired whenever the user's location is updated on the map
  • UserLocationChangedEventArgs - Event arguments containing the Location property

Platform Implementation

  • iOS: Subscribes to MKMapView.DidUpdateUserLocation delegate method
  • Android: Subscribes to GoogleMap.MyLocationChange event

Usage

var map = new Map { IsShowingUser = true };

map.UserLocationChanged += (sender, e) =>
{
    Console.WriteLine($"User location: {e.Location.Latitude}, {e.Location.Longitude}");
    
    // Or access the last known location directly
    var lastLocation = map.LastUserLocation;
};

Changes

  • Added LastUserLocation property and UserLocationUpdated method to IMap interface
  • Added LastUserLocation property and UserLocationChanged event to Map class
  • Created UserLocationChangedEventArgs class
  • iOS: Subscribe to DidUpdateUserLocation in MauiMKMapView
  • Android: Subscribe to MyLocationChange in MapHandler.Android
  • Added 7 unit tests
  • Added UserLocationGallery sample page

Issues Fixed

Addresses #14700

Part of Maps Epic

Part of #33787 (Epic: Maps Control Improvements for .NET 11)

Copilot AI review requested due to automatic review settings January 30, 2026 17:37
@jfversluis jfversluis added this to the .NET 11.0-preview1 milestone Jan 30, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a public way to observe the Map control’s user-location updates (event + last-known location) when IsShowingUser is enabled, with iOS/Android handler wiring and sample/test coverage.

Changes:

  • Introduces Map.LastUserLocation, Map.UserLocationChanged, and UserLocationChangedEventArgs in Controls.Maps.
  • Extends the core IMap contract and wires platform callbacks on iOS (DidUpdateUserLocation) and Android (MyLocationChange).
  • Adds unit tests and a new sample gallery page demonstrating the feature.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt Declares new IMap members in netstandard API surface.
src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Declares new IMap members in net API surface.
src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt Declares new IMap members in Windows API surface.
src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Declares new IMap members in Tizen API surface.
src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Declares new IMap members in MacCatalyst API surface.
src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt Declares new IMap members in iOS API surface.
src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt Declares new IMap members in Android API surface.
src/Core/maps/src/Platform/iOS/MauiMKMapView.cs Hooks iOS user-location updates and forwards them to the virtual view.
src/Core/maps/src/Handlers/Map/MapHandler.Android.cs Hooks Android location change event and forwards updates to the virtual view.
src/Core/maps/src/Core/IMap.cs Adds LastUserLocation and UserLocationUpdated to the core map interface.
src/Controls/tests/Core.UnitTests/MapTests.cs Adds unit tests validating event firing and location storage behavior.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/UserLocationGallery.xaml.cs Adds code-behind demonstrating UserLocationChanged and LastUserLocation.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/UserLocationGallery.xaml Adds sample UI for displaying live user-location updates.
src/Controls/samples/Controls.Sample/Pages/Controls/MapsGalleries/MapsGallery.cs Adds navigation entry for the new User Location sample page.
src/Controls/Maps/src/UserLocationChangedEventArgs.cs Introduces new EventArgs type carrying the updated Location.
src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in netstandard surface.
src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in net surface.
src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in Windows surface.
src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in Tizen surface.
src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in MacCatalyst surface.
src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in iOS surface.
src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt Declares new Controls.Maps APIs (property/event/eventargs) in Android surface.
src/Controls/Maps/src/Map.cs Adds UserLocationChanged event and LastUserLocation property to Map.
src/Controls/Maps/src/HandlerImpl/Map.Impl.cs Implements the new IMap members and raises the new event.

Comment thread src/Core/maps/src/Core/IMap.cs
Comment thread src/Controls/Maps/src/Map.cs
Comment thread src/Controls/Maps/src/HandlerImpl/Map.Impl.cs
@rmarinho
Copy link
Copy Markdown
Member

/rebase

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 3 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 26, 2026

Independent Code Review

Summary: Adds UserLocationChanged event and LastUserLocation property to Map. Android uses GoogleMap.MyLocationChange, iOS uses MKMapView.DidUpdateUserLocation.

✅ What Looks Good

  • Proper de-duplication: UserLocationUpdated checks Equals(_lastUserLocation, location) before raising
  • Correct OnPropertyChanging/OnPropertyChanged notifications for binding support
  • Both Android and iOS implementations properly subscribe/unsubscribe
  • Good test coverage (7 tests)

🔴 Issues

1. LastUserLocation is not a BindableProperty
It's a plain CLR property backed by a _lastUserLocation field. While OnPropertyChanged is called, it can't be used as a binding target in XAML (e.g. {Binding LastUserLocation} won't work on the Map directly since it's not a BP). This is inconsistent with other Map properties.

2. Android MyLocationChange is deprecated
GoogleMap.MyLocationChangeEventArgs is deprecated in favor of FusedLocationProviderClient. This will generate warnings and may be removed in future Google Play Services updates.

3. No Windows implementation
Location updates won't fire on Windows. No unsupported platform attribute or documentation noting this.

4. High-frequency event with no throttling
GPS updates can fire very frequently. The Equals check helps only for identical coordinates. Consider documenting that consumers should throttle/debounce if using for UI updates.

🟡 Nits

  • The Equals comparison on Location — does Location.Equals compare all properties (lat, lon, altitude, accuracy, etc.) or just lat/lon? If it compares everything, minor accuracy changes will fire events. If it only compares lat/lon, it might miss meaningful updates.

@jfversluis
Copy link
Copy Markdown
Member Author

Thanks for the detailed review @kubaflo!

1. LastUserLocation is not a BindableProperty
This is intentional and consistent with the \VisibleRegion\ pattern already in Map. \VisibleRegion\ is also a plain CLR property with \OnPropertyChanged, not a BindableProperty. Both represent values that are set by the platform handler, not by the user — making them BindableProperties would imply you can bind to them (set them), which would be misleading. The \OnPropertyChanging/\OnPropertyChanged\ calls enable data binding from them in MVVM scenarios (e.g., {Binding LastUserLocation, Source={x:Reference myMap}}).

2. Android MyLocationChange is deprecated
Good catch. The \FusedLocationProviderClient\ is the recommended replacement, but it requires Google Play Services dependency and significantly more complex lifecycle management (separate location permission handling, client connection, etc.). For the initial implementation, \GoogleMap.MyLocationChange\ is simpler and still functional. A follow-up PR can migrate to \FusedLocationProviderClient\ as a separate improvement.

3. No Windows implementation
Same as other Maps PRs — Windows Maps (\MapControl) doesn't expose user location change events natively. This would require polling or integrating with \Geolocator, which is a separate API concern.

4. High-frequency event with no throttling
The \Equals\ check already deduplicates identical coordinates. The GPS update frequency is controlled by the platform handler's subscription, not the event itself. Consumers who need throttling can use \System.Reactive\ or a simple timer-based debounce. Adding throttling at the control level would make the behavior opaque and harder to reason about. I'll add a remark in the XML docs suggesting consumers debounce if needed.

5. Location.Equals comparison
\Location.Equals\ compares Latitude and Longitude only (the two properties that identify a geographic position). Altitude, accuracy, speed, etc. are not part of the equality comparison, which is the correct behavior for this use case.

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 27, 2026

🔍 Round 2 Review — PR #33799 (UserLocationChanged)

Updated with author response analysis

✅ What Looks Good

  • Dedicated UserLocationChangedEventArgs type — good design choice.
  • Proper null-check on Android: if (e.Location != null && VirtualView != null).
  • iOS implementation through MKMapView.DidUpdateUserLocation is the correct iOS API.
  • PropertyChanged/PropertyChanging notifications for LastUserLocation enable data binding scenarios.

⚠️ Issues & Author Responses

1. LastUserLocation is not a BindablePropertyRetracted
Author correctly points out this is consistent with the existing VisibleRegion pattern — also a plain CLR property with OnPropertyChanged, not a BindableProperty. Both represent values set by the platform handler, not by the user. Making them BindableProperty would imply they can be bound to (set), which is misleading. OnPropertyChanged calls still enable data binding from them (e.g., {Binding LastUserLocation, Source={x:Reference myMap}}). This is the right pattern. ✅

2. Android uses deprecated GoogleMap.MyLocationChange eventAcknowledged, follow-up planned
Author agrees this should move to FusedLocationProviderClient but notes it requires significantly more complex lifecycle management (separate location permission handling, client connection). Using the simpler deprecated API for the initial implementation is pragmatic. Follow-up PR planned. ✅

3. No Windows implementationAcknowledged as platform limitation
Windows MapControl doesn't expose user location change events natively. Would require separate Geolocator API integration. ✅

4. High-frequency event with no throttlingRetracted
Author makes a strong case: GPS update frequency is controlled by the platform handler's subscription, not the event itself. Adding throttling at the control level would make behavior opaque and harder to reason about. Consumers can use System.Reactive or timer-based debounce. This is the correct separation of concerns. ✅

5. Location equality dedupPartially retracted
Author states Location.Equals compares Latitude and Longitude only, so identical coordinates won't trigger redundant UI updates through the binding system. However, note that the UserLocationChanged event itself still fires for every platform callback, even with identical coordinates — there's no dedup before Invoke. This is consistent with how MapClicked works (no dedup there either), so it's acceptable.

6. Sample uses Debug.WriteLineStill noted (minor)
The sample page uses Debug.WriteLine which is stripped in Release. Consider displaying info in UI only or using ILogger. Minor — this is sample code, not library code.

Summary

The author's VisibleRegion precedent argument was convincing — my most substantive concern was based on an incorrect assumption about the codebase's patterns. The remaining items are informational/minor. This PR is in good shape.

@jfversluis
Copy link
Copy Markdown
Member Author

/azp run maui-pr

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

kubaflo
kubaflo previously approved these changes Feb 27, 2026
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Feb 27, 2026

📋 Round 3 — PR #33799 (UserLocationChanged) — Final Recommendation

No outstanding issues. All Round 2 concerns were satisfactorily addressed.

Note on CI: This PR has a maui-pr-devicetests failure specifically in CoreCLR iOS Helix Tests. All other device tests pass (Android, MacCatalyst, Windows, Mono iOS). This looks like a flaky iOS CoreCLR Helix test — worth re-running to confirm it's not related to this PR's iOS MKMapView.DidUpdateUserLocation changes.

Recommendation: Re-run maui-pr-devicetests once to rule out flakiness, then merge. If iOS CoreCLR fails again, investigate whether the UserLocationChanged event handler on iOS has any CoreCLR-specific issue.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 33799

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 33799"

@jfversluis jfversluis force-pushed the feature/map-user-location branch from 5262a29 to 3f16dd0 Compare March 2, 2026 10:53
jfversluis and others added 4 commits March 2, 2026 14:18
- Add LastUserLocation property to IMap interface and Map control
- Add UserLocationChanged event fired when user location updates
- Add UserLocationChangedEventArgs class
- iOS: Subscribe to MKMapView.DidUpdateUserLocation
- Android: Subscribe to GoogleMap.MyLocationChange
- Add 7 unit tests for UserLocationChanged functionality
- Add UserLocationGallery sample page

Addresses #14700
- Add PropertyChanging/PropertyChanged notifications when LastUserLocation changes
- Skip update if location hasn't changed (mirror VisibleRegion pattern)
- Clarify XML docs about when UserLocationChanged fires
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make event handler parameters nullable (object?)
- Use fully-qualified Location type to avoid XAML sourcegen conflict

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis force-pushed the feature/map-user-location branch from 3f16dd0 to 62b01f2 Compare March 2, 2026 13:21
@jfversluis jfversluis disabled auto-merge March 2, 2026 19:28
@jfversluis jfversluis merged commit 84c0dba into net11.0 Mar 2, 2026
25 of 29 checks passed
@jfversluis jfversluis deleted the feature/map-user-location branch March 2, 2026 19:29
@dotnet dotnet deleted a comment from dotnet-policy-service bot Mar 2, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Apr 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants