Skip to content

Fix NullReferenceException when tapping Map with no overlays on iOS#34969

Merged
jfversluis merged 1 commit into
net11.0from
fix/map-overlay-nullref-34910
Apr 17, 2026
Merged

Fix NullReferenceException when tapping Map with no overlays on iOS#34969
jfversluis merged 1 commit into
net11.0from
fix/map-overlay-nullref-34910

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

Fixes #34910

MKMapView.Overlays returns null (not an empty array) when no overlays have been added to the map. The OnMapClicked handler in MauiMKMapView.cs iterated .Overlays without a null check, causing a NullReferenceException when tapping the map.

This was introduced by the "Add Circle, Polygon, and Polyline click events" feature (#29101) on the net11.0 branch.

Changes

Fix (MauiMKMapView.cs):

  • Added null check for mauiMkMapView.Overlays before iterating, following the existing null guard pattern from ClearMapElements() at line 322.

Tests (MapTests.cs):

  • VerifiesIMap.Clicked does not throw when the map has no elementsClickedDoesNotThrowWithNoElements
  • Verifies theMapClicked event fires correctly with proper location data when no map elements existClickedFiresEventWithNoElements

Root Cause

The OnMapClicked static method (line 568) did:

foreach (var overlay in mauiMkMapView.Overlays) // NullRef when Overlays is null

MKMapView.Overlays is a native iOS property that returns null when no overlays exist (unlike .NET collections which typically return empty). The fix stores the value first and returns early if null:

var overlays = mauiMkMapView.Overlays;
if (overlays is null)
    return;

foreach (var overlay in overlays)

This matches the existing pattern in ClearMapElements():

var elements = Overlays;
if (elements == null)
    return;

Copilot AI review requested due to automatic review settings April 15, 2026 08:10
@github-actions

github-actions Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

🚀 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 -- 34969

Or

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an iOS/MacCatalyst crash when tapping a Map with no overlays by guarding against MKMapView.Overlays being null.

Changes:

  • Added a null-check around mauiMkMapView.Overlays before enumerating overlays during tap hit-testing on iOS.
  • Added two Map unit tests around IMap.Clicked / MapClicked behavior when the map has no elements.

Reviewed changes

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

File Description
src/Core/maps/src/Platform/iOS/MauiMKMapView.cs Prevents NullReferenceException by handling Overlays == null before overlay hit-testing.
src/Controls/tests/Core.UnitTests/MapTests.cs Adds managed-unit tests for map click invocation/event behavior when no elements exist (but does not execute the iOS handler code path).

Comment thread src/Controls/tests/Core.UnitTests/MapTests.cs
Comment thread src/Controls/tests/Core.UnitTests/MapTests.cs
@jfversluis
jfversluis force-pushed the fix/map-overlay-nullref-34910 branch from e9858ae to 634fd64 Compare April 15, 2026 08:55
@jfversluis jfversluis added this to the .NET 11.0-preview4 milestone Apr 15, 2026
@jfversluis jfversluis added the area-controls-map Map / Maps label Apr 15, 2026
@kubaflo

kubaflo commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Code Review — PR #34969

Independent Assessment

What this changes: Adds a null check for mauiMkMapView.Overlays in MauiMKMapView.OnMapClicked (iOS) before iterating the overlay collection for hit-testing. MKMapView.Overlays returns null (not an empty array) when no overlays exist. Also adds unit tests and an iOS device test.

Inferred motivation: Tapping a Map with no MapElements on iOS crashes with NullReferenceException because OnMapClicked iterates .Overlays without a null check.

Reconciliation with PR Narrative

Author claims: MKMapView.Overlays returns null when no overlays exist; OnMapClicked didn't guard against this. Introduced by PR #29101 (circle/polygon/polyline click events).

Agreement: Root cause matches exactly. The fix follows the existing ClearMapElements() pattern at line 139-142.

Findings

✅ Correct — Early return is safe; click event fires BEFORE overlay iteration

The method structure is:

1. Fire IMap.Clicked event (line 558)
2. Hit-test overlays for element-specific click (line 568+)

The null check and return at line 569-570 only skips the overlay hit-testing, which is correct — there are no overlays to test. The IMap.Clicked event has already been dispatched at line 558, so the MapClicked event still fires on maps without overlays.

✅ Correct — Fix follows existing codebase pattern

The null guard mirrors ClearMapElements() at line 139-142:

var elements = Overlays;
if (elements == null)
    return;

Storing in a local variable before null-checking is the correct iOS pattern — prevents potential TOCTOU if the native property could change between check and use.

✅ Correct — Device test exercises the actual crash path

MapTests.iOS.cs registers Map handlers, creates a Map with no elements, and invokes OnMapClicked via reflection. The author confirmed this test fails without the fix (NullReferenceException) and passes with it. This is proper regression test coverage.

✅ Correct — Unit tests provide supplementary managed-layer coverage

The two unit tests verify IMap.Clicked doesn't throw and fires MapClicked correctly at the control layer. These don't exercise the iOS crash path directly (as the copilot-reviewer correctly noted and the author acknowledged), but they validate the event contract.

⚠️ Warning — Device test uses reflection on private static method

OnMapClicked is accessed via BindingFlags.Static | BindingFlags.NonPublic. If the method is renamed or its signature changes, the test silently falls through to the else branch (creates a new recognizer and calls the method — which may still work but with different semantics). This is a known fragility, acceptable for testing a private method without changing its visibility.

💡 Suggestion — Maps project references added to Controls.DeviceTests

Adding Maps.csproj and Controls.Maps.csproj to the device tests project increases build scope. This is the right call — Maps has zero device test coverage today, and this establishes the foundation. Just worth noting for CI build time awareness.

CI Status

CI failures are infrastructure-level — SDK resolver errors (Microsoft.Build.NoTargets, Microsoft.DotNet.Arcade.Sdk not found) affecting all macOS builds on the net11.0 branch. Completely unrelated to this PR's code changes.

Devil's Advocate

  1. Are there other places where .Overlays is iterated without null check? Only two references in the file: ClearMapElements() (already guarded) and OnMapClicked() (fixed here). No other unguarded access.

  2. Could the return cause issues if future code is added after the loop? If someone adds code after the overlay foreach loop that should always run, the early return would skip it. But currently there's nothing after the loop — the method ends. The code is well-commented, reducing this risk.

  3. Does this need to target main instead of net11.0? The bug was introduced by Add Circle, Polygon, and Polyline click events for Map control #29101 which landed on net11.0. The fix correctly targets the same branch. If the code was cherry-picked to main, a corresponding fix would be needed there too.

Verdict: LGTM

Confidence: high

Summary: Clean, minimal one-line null check fix for a genuine NullReferenceException crash. Follows the established pattern in the same file. The IMap.Clicked event correctly fires before the overlay iteration, so the early return doesn't suppress map tap events. Device test directly exercises the crash path. CI failures are infrastructure-level, not PR-related.

@jfversluis

Copy link
Copy Markdown
Member Author

Waiting for #34978 so we can run the tests

@jfversluis

Copy link
Copy Markdown
Member Author

/rebase

MKMapView.Overlays returns null (not an empty array) when no overlays
have been added. The OnMapClicked handler iterated Overlays without
a null check, causing NullReferenceException on tap.

Follow the existing null guard pattern from ClearMapElements.

Fixes #34910

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
github-actions Bot force-pushed the fix/map-overlay-nullref-34910 branch from 634fd64 to e3bcecc Compare April 16, 2026 13:18
@jfversluis

Copy link
Copy Markdown
Member Author

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

@jfversluis
jfversluis merged commit a344df9 into net11.0 Apr 17, 2026
109 of 145 checks passed
@jfversluis
jfversluis deleted the fix/map-overlay-nullref-34910 branch April 17, 2026 08:51
PureWeen pushed a commit that referenced this pull request Apr 17, 2026
When a PR's merge commit is not on any release branch, the script
was reading Versions.props at the merge commit. This gives stale
values — e.g. a PR merged to inflight/current when PatchVersion=60
would get milestoned SR6, even though main has since moved to 70.

Now uses the PR's base.ref to determine which branch to read from:
  main, inflight/*, darc/* → origin/main
  net11.0 → origin/net11.0
  release/* → that branch directly

This ensures .NET 11 PRs always read from net11.0 (not main),
and inflight PRs read from main (not the stale merge commit).
Release branch detection still takes priority.

Validated:
- #34228 (inflight/current) → reads main → SR7 (was incorrectly SR6)
- #34969 (net11.0) → reads net11.0 → preview1
- #34620 (main, on SR6 branch) → release branch → SR6 (unchanged)

91 Pester tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 21, 2026
Two fixes to milestone detection:

1. Fallback reads Versions.props from the development branch (main
   for .NET 10, net11.0 for .NET 11) instead of origin/{base.ref}.
   Staging branches like inflight/candidate have stale PatchVersion
   but their PRs ultimately ship on main's version.

2. Find-ReleaseBranchForCommit falls back to git log --grep when
   ancestry check fails. Handles rebases and cherry-picks where the
   commit SHA changes but the PR number '(#NNNNN)' is preserved in
   the commit message.

Updated live validation guide to match new behavior.

Validated:
- #34667 (rebased to SR6) → found via grep → .NET 10 SR6 ✅
- #34959 (inflight/candidate) → reads main → .NET 10 SR7 ✅
- #34620 (on SR6 branch) → ancestry → .NET 10 SR6 ✅
- #34969 (net11.0) → reads net11.0 → .NET 11.0-preview4 ✅

91 Pester tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Apr 21, 2026
…ng branch (#35054)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Summary

Fix milestone fallback for `inflight/*` and `darc/*` branches to read
from `origin/main` instead of the staging branch itself.

## Problem

PRs merged to `inflight/candidate` were milestoned as SR6 because
`inflight/candidate` has `PatchVersion=60`. But those PRs will
ultimately ship in SR7 (when the Candidate merges to `main`, which is at
`PatchVersion=70`).

## Fix

For staging branches (`inflight/*`, `darc/*`), read `Versions.props`
from `origin/main` instead of `origin/{base.ref}`. These branches always
feed into main, so main's version is the correct target.

All other branches continue reading from `origin/{base.ref}` directly.

## Validated

| PR | Base | Before | After |
|---|---|---|---|
| #34959 | inflight/candidate | SR6 (wrong) | SR7 ✅ |
| #35040 | inflight/current | SR7 | SR7 ✅ |
| #34969 | net11.0 | preview1 | preview4 ✅ |
| #34620 | main (on SR6 branch) | SR6 | SR6 ✅ |

91 Pester tests pass.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators May 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants