[Android] Fix for ToolbarItem retaining the icon from the previous page on Android when using NavigationPage.#32311
Conversation
jsuarezruiz
left a comment
There was a problem hiding this comment.
Could include a related test?
@jsuarezruiz, This issue occurs in a random manner, so it's not possible to add a test case for it. |
There was a problem hiding this comment.
Pull Request Overview
This PR adds race condition prevention for toolbar item icon loading during navigation on Android. The fix prevents stale async icon loading callbacks from updating the wrong toolbar items when rapid navigation changes occur.
Key changes:
- Introduces a static dictionary tracking MenuItem ID to ToolbarItem associations using weak references
- Adds cleanup logic for disposed MenuItems to remove their entries from the tracking map
- Implements validation in UpdateMenuItemIcon to verify that async icon callbacks are still targeting the correct ToolbarItem
| { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
The race condition check has a gap: if the MenuItem ID is reused for a new ToolbarItem before the async callback executes, but the WeakReference has been garbage collected, the check will pass even though it should abort. Add an additional check: if the menuItem.ItemId exists in the map but the WeakReference target is null (collected), the callback should return early rather than proceeding with the update.
| // Additional check: if the menuItem.ItemId exists in the map but the WeakReference target is null (collected), abort | |
| if (_menuItemToolbarItemMap.TryGetValue(menuItem.ItemId, out var wr) && !wr.TryGetTarget(out _)) | |
| { | |
| return; | |
| } |
|
|
||
| // Track which ToolbarItem should currently be associated with each MenuItem ID to prevent race conditions | ||
| // This prevents stale async icon loading callbacks from updating the wrong toolbar items during navigation | ||
| static readonly Dictionary<int, WeakReference<ToolbarItem>> _menuItemToolbarItemMap = new(); |
There was a problem hiding this comment.
The static dictionary _menuItemToolbarItemMap is accessed from multiple threads (UI thread for updates, background threads for async icon loading) without synchronization. This creates a potential race condition where dictionary operations could throw or corrupt data. Consider using ConcurrentDictionary<int, WeakReference> instead to ensure thread-safe access.
| if (menu.FindItem(previousMenuItem.ItemId) == null) | ||
| { | ||
| // Clean up the mapping for disposed MenuItems | ||
| _menuItemToolbarItemMap.Remove(previousMenuItem.ItemId); |
There was a problem hiding this comment.
Cleanup operations occur in a loop that iterates backwards through previousMenuItems, resulting in multiple individual dictionary removals. If many MenuItems are being disposed (e.g., during navigation), consider collecting the IDs first and then removing them in a batch, or at minimum, verify the operation succeeded to handle cases where the ID might not exist in the map.
| _menuItemToolbarItemMap.Remove(previousMenuItem.ItemId); | |
| if (!_menuItemToolbarItemMap.Remove(previousMenuItem.ItemId)) | |
| { | |
| // Optionally log or handle the case where the mapping does not exist | |
| // e.g., Debug.WriteLine($"Warning: MenuItem mapping for ItemId {previousMenuItem.ItemId} not found during cleanup."); | |
| } |
d6005af to
bc6f696
Compare
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 32311Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 32311" |
|
/rebase |
bc6f696 to
0450171
Compare
|
/rebase |
0450171 to
fccff41
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — fix-31727-Made changes to fix race condition logic. ·
|
| Concern | Source | Assessment |
|---|---|---|
| Thread safety (ConcurrentDictionary needed) | copilot-pull-request-reviewer | ❌ Not valid - UI thread only |
| Race detection gap (GC'd WeakRef) | copilot-pull-request-reviewer | ❌ Not valid - code handles it correctly |
| Cleanup efficiency (batch operations) | copilot-pull-request-reviewer | 💡 Minor optimization, not required |
| Missing tests | jsuarezruiz, prior agent |
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #32311 | Track MenuItem→ToolbarItem with WeakReference dictionary, validate before applying async icon | ⏳ PENDING (Gate) | ToolbarExtensions.cs (+26) |
Original PR |
Platform Selection for Gate
Selected Platform: Android
Rationale: Issue is Android-specific; PR modifies Android-only file (Platform/Android/Extensions/ToolbarExtensions.cs); current host is macOS which supports Android testing.
🚦 Gate — Test Verification
📝 Review Session — fix-31727-Made changes to fix race condition logic. · fccff41
Result: ❌ FAILED
Platform: android
Reason: No tests exist for this PR
Gate Check
Searched for tests matching issue #31727 and PR #32311:
src/Controls/tests/TestCases.HostApp/Issues/Issue31727.*— Not foundsrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31727.*— Not found- No test files referencing issue number 31727
Context
This PR addresses a random race condition in Android toolbar icon loading during NavigationPage navigation. The PR author stated: "This issue occurs in a random manner, so it's not possible to add a test case for it."
Why a Test Is Still Possible
Even though the race condition itself is timing-dependent and hard to trigger reliably:
- Navigation scenario test: A UI test that navigates from PageA (3 toolbar items) to PageB (2 toolbar items) and verifies PageB shows the correct icons serves as a regression test.
- Structural test: Verifies the WeakReference dictionary cleanup mechanism works correctly.
- Device test: A toolbar navigation test that verifies toolbar items update correctly when navigating.
Existing related device tests are in src/Controls/tests/DeviceTests/Elements/Toolbar/ToolbarTests.cs, but none cover the specific navigation-with-different-item-count scenario.
Recommendation
The PR author should add at minimum a UI test that:
- Navigates between two pages with different toolbar item counts
- Verifies the destination page shows the correct toolbar items after navigation
- Can serve as a regression test for this race condition
The write-ui-tests skill can help generate appropriate tests.
Status
Gate ❌ FAILED — proceeding to Report phase per autonomous execution rules.
🔧 Fix — Analysis & Comparison
📝 Review Session — fix-31727-Made changes to fix race condition logic. · fccff41
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #32311 | Track MenuItem→ToolbarItem mapping with WeakReference dictionary | ⏳ PENDING (Gate) | ToolbarExtensions.cs (+26) |
Original PR |
Exhausted: N/A — Skipped due to Gate ❌ FAILED
Selected Fix: PR's fix — Gate failed, Fix phase skipped. Code analysis confirms PR's implementation is correct.
Reason for Skip: Gate failed because no tests exist for this issue. Without tests, try-fix cannot run its two-pass verification (FAIL without fix → PASS with fix). Per autonomous execution rules, skipping Fix phase and proceeding to Report.
Note on Code Quality: Independent code analysis (see Report) found the PR's implementation to be correct and well-reasoned, despite Gate failure.
📋 Report — Final Recommendation
📝 Review Session — fix-31727-Made changes to fix race condition logic. · fccff41
⚠️ Final Recommendation: REQUEST CHANGES
Summary
PR #32311 fixes a real and verified race condition on Android where toolbar icons from a previous page persist on the new page during NavigationPage navigation. The implementation is technically sound and correctly addresses the root cause. However, no tests exist for this issue, making it impossible to verify the fix works or prevent future regressions.
Root Cause Analysis
When navigating between pages on Android with different toolbar item counts (e.g., 3 → 2 items), Android reuses MenuItem objects for performance. The FontImageSource icon loading uses async callbacks via LoadImage → FireAndForget. These callbacks can complete after the new page's toolbar has been set up, causing old icons to overwrite the correct new ones. The timing dependency (async completion vs. navigation speed) makes this a random race condition.
Gate Result
❌ FAILED — No tests found for issue #31727 or PR #32311.
Searched: TestCases.HostApp/Issues/Issue31727.*, TestCases.Shared.Tests/Tests/Issues/Issue31727.* — both not found.
Fix Quality Assessment
Independent code review of the PR's implementation:
| Aspect | Rating | Notes |
|---|---|---|
| Root Cause Understanding | ✅ Excellent | Clear, accurate explanation of race condition |
| Solution Approach | ✅ Correct | WeakReference tracking is appropriate for this scenario |
| Thread Safety | ✅ Acceptable | LoadImage callbacks run on main thread (no ConfigureAwait(false)) |
| Race Detection Logic | ✅ Correct | !weakRef.TryGetTarget(...) || !ReferenceEquals(...) correctly handles both GC'd and reused cases |
| Memory Management | ✅ Good | Cleanup in both disposal paths prevents leaks |
| Code Quality | ✅ Good | Clear comments explain WHY, not just WHAT |
Analysis of Prior Review Concerns
The prior agent review (2026-02-16) raised two technical concerns. Re-analysis shows both are not valid:
1. Thread Safety (prior: ConcurrentDictionary required)
"The static dictionary _menuItemToolbarItemMap is accessed from multiple threads without synchronization."
Re-analysis: UpdateMenuItemIcon is called from UpdateMenuItem → UpdateMenuItems, which are UI thread operations. The LoadImage method uses FireAndForget(LoadImageResult), and LoadImageResult awaits the task without ConfigureAwait(false), so continuations execute on the captured main-thread SynchronizationContext. The dictionary is only accessed from the UI thread. No change needed.
2. Race Detection Gap (prior: GC'd WeakRef passes incorrectly)
"If target was garbage collected, the check will pass even though it should abort."
Re-analysis: The actual code is:
if (!weakRef.TryGetTarget(out var currentToolbarItem) || !ReferenceEquals(currentToolbarItem, toolBarItem))
{
return;
}When GC'd: TryGetTarget returns false → !false = true → returns early (aborts). The code already correctly handles this case. No change needed.
Remaining Issue: Missing Tests
This is the primary blocker. Even for random race conditions, a test can be written that:
- Creates a
NavigationPagewith PageA (3 toolbar items withFontImageSourceicons) - Pushes PageB (2 toolbar items with different icons)
- Verifies PageB shows the correct toolbar items (not PageA's icons)
- Pops back and pushes PageB multiple times to stress-test
While this won't reliably trigger the race condition timing, it:
- Documents the expected behavior
- Provides a regression test for the tracking mechanism
- Verifies correct toolbar items are shown after navigation
- Can be run with
TestCases.HostApp+TestCases.Shared.Tests(UI test)
Suggested test approach: Issue31727.cs in TestCases.HostApp/Issues/ + matching test in TestCases.Shared.Tests/Tests/Issues/. The write-ui-tests skill can help generate this.
What Needs to Change
Required:
- ✅ Add a UI test (
IssueXXXXX.cs) that navigates between pages with different toolbar item counts and verifies icons are correct
Optional (nice-to-have):
2. Consider whether the static dictionary scope is safe in multi-window Android scenarios (MenuItem IDs could theoretically collide across different NavigationPage instances)
Platform Coverage
Affected: Android only
PR Scope: Correctly scoped to Platform/Android/Extensions/ToolbarExtensions.cs
Status
write-ui-tests skill.
📋 Expand PR Finalization Review
Title: ✅ Good
Current: [Android] Fix for ToolbarItem retaining the icon from the previous page on Android when using NavigationPage.
Description: ✅ Good
- Ends with a period (.) — commit message headlines should not end with punctuation
- "on Android" is redundant —
[Android]prefix already conveys this - Slightly verbose — could be tighter
✨ Suggested PR Description
[!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!
Root Cause
On Android, ToolbarExtensions.UpdateMenuItems reuses existing MenuItem objects for performance when the new page has fewer toolbar items than the previous page. Icon loading via FontImageSource / LoadImage is async, so a loading callback from the previous page's toolbar can fire after the current page has already reassigned those MenuItem objects to its own ToolbarItem instances — overwriting the correct icons with stale ones. The race is timing-dependent and occurs more frequently when navigating from a page with more items to one with fewer (e.g., 3 → 2).
Description of Change
Adds a lightweight tracking mechanism in ToolbarExtensions to associate each MenuItem (by its ID) with its currently-assigned ToolbarItem via a WeakReference<ToolbarItem>. When an async icon-loading callback fires inside UpdateMenuItemIcon, it checks whether the MenuItem is still associated with the same ToolbarItem that initiated the load. If the association has changed (i.e., the MenuItem was reused for a different item after navigation) or the ToolbarItem was garbage-collected, the callback returns early without updating the icon.
Cleanup of map entries happens in both disposal paths in UpdateMenuItems:
- When Android silently removes menu items after activity switches
- When excess items are removed after the new page's items are applied
This preserves the existing MenuItem reuse performance optimization while preventing stale callbacks from contaminating the current page's icons.
Note: The race condition is non-deterministic, so it is not possible to add a deterministic automated test for it.
What NOT to Do (for future agents)
- ❌ Don't remove MenuItem reuse — reusing
MenuItemobjects is an intentional performance optimization inUpdateMenuItems; any fix must work alongside it, not against it. - ❌ Don't use strong references in the map — using strong references to
ToolbarItemin the dictionary would create memory leaks if map entries are not cleaned up promptly; useWeakReference<ToolbarItem>. - ❌ Don't check the ID alone — simply checking whether the
MenuItem.ItemIdis in the map is insufficient; you must also verify the trackedToolbarItemis reference-equal to the one that initiated the load.
Issues Fixed
Fixes #31727
Platforms Tested
- Android
- Windows
- iOS
- Mac
Code Review: ⚠️ Issues Found
Code Review — PR #32311
File: src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs
🔴 Critical Issues
Thread Safety: Static Dictionary Is Not Thread-Safe
Location: Line 30 — _menuItemToolbarItemMap declaration, and all read/write sites
Problem:
_menuItemToolbarItemMap is a static readonly Dictionary<int, WeakReference<ToolbarItem>>. It is written on the UI thread (UpdateMenuItem, UpdateMenuItems) but read (and potentially written via Remove) from within the LoadImage callback in UpdateMenuItemIcon. MAUI's LoadImage callbacks are not guaranteed to be dispatched on the UI thread in all implementations (they depend on the image source type and loader). If a callback fires on a background thread concurrently with a UI-thread dictionary modification, this can throw InvalidOperationException or silently corrupt the dictionary state.
Current code:
static readonly Dictionary<int, WeakReference<ToolbarItem>> _menuItemToolbarItemMap = new();Recommendation:
Use ConcurrentDictionary<int, WeakReference<ToolbarItem>> to make all operations thread-safe without requiring explicit locking:
static readonly ConcurrentDictionary<int, WeakReference<ToolbarItem>> _menuItemToolbarItemMap = new();All existing call sites ([] assignment, Remove, TryGetValue) have direct equivalents on ConcurrentDictionary with no other changes needed.
🟡 Suggestions
1. Missing Guard When Map Entry Is Absent
Location: UpdateMenuItemIcon, starting at the TryGetValue check (~line 385)
Problem:
The current guard only fires when the menuItem.ItemId IS present in the map:
if (_menuItemToolbarItemMap.TryGetValue(menuItem.ItemId, out var weakRef))
{
if (!weakRef.TryGetTarget(out var currentToolbarItem) || !ReferenceEquals(currentToolbarItem, toolBarItem))
{
return;
}
}
// Falls through to update the icon if ID is NOT in the mapIf the entry was already removed (because the MenuItem was disposed and cleaned up) before the callback fires, TryGetValue returns false, the guard is skipped, and the callback proceeds to call menuItem.SetIcon(...) on a MenuItem that may be stale or disposed. The menuItem.IsAlive() check above this block will catch truly disposed items, but there is a window between cleanup and disposal.
Recommendation:
Consider returning early when the ID is not found in the map (i.e., treat an absent entry as a signal that the item was already cleaned up):
if (!_menuItemToolbarItemMap.TryGetValue(menuItem.ItemId, out var weakRef)
|| !weakRef.TryGetTarget(out var currentToolbarItem)
|| !ReferenceEquals(currentToolbarItem, toolBarItem))
{
return;
}This is a safe change only if UpdateMenuItemIcon is always called after the map entry is set in UpdateMenuItem. Looking at the code flow — the map entry is set on line ~350, and UpdateMenuItemIcon is called immediately after on line ~354 — so this ordering is guaranteed for the default path.
2. Custom updateMenuItemIcon Callback Bypasses Race Condition Protection
Location: UpdateMenuItem — the updateMenuItemIcon branch (~line 354)
Problem:
The race condition guard is only present in the default UpdateMenuItemIcon path. If a caller provides a custom updateMenuItemIcon action, that action may also perform async icon loading without the guard, re-introducing the same race condition for callers that override the default behavior.
Recommendation:
Document this limitation at the updateMenuItemIcon parameter or consider wrapping custom callbacks so they also validate against the map. At minimum, add a code comment:
// NOTE: Custom updateMenuItemIcon callbacks are responsible for their own
// race condition handling. The _menuItemToolbarItemMap guard only applies
// to the default UpdateMenuItemIcon path.
if (updateMenuItemIcon != null)
updateMenuItemIcon(context, menuitem, item);
else
UpdateMenuItemIcon(mauiContext, menuitem, item, tintColor);✅ Looks Good
- WeakReference logic is correct: The check
!weakRef.TryGetTarget(out var currentToolbarItem) || !ReferenceEquals(currentToolbarItem, toolBarItem)correctly handles both the GC'd case (returns early) and the reused-for-different-item case (returns early). The Copilot reviewer comment suggesting the GC'd case isn't handled is incorrect — it is handled by the!weakRef.TryGetTarget(...)branch of the||. - Both cleanup paths are covered: Map entries are removed both when Android silently deletes menu items (the
FindItem == nullloop) and when excess items are trimmed (thewhileloop). No leaks introduced. menuitem.IsAlive()guard is preserved: The existing alive check remains in place as a first line of defense before the map check.- WeakReference prevents memory leaks: Choosing
WeakReference<ToolbarItem>over a strong reference is correct — it allowsToolbarItemto be GC'd naturally without being kept alive by the map. - Map entry is set before icon loading begins: In
UpdateMenuItem,_menuItemToolbarItemMap[menuitem.ItemId]is assigned beforeUpdateMenuItemIconis called, so the guard in the callback will always find the entry for fresh assignments.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please add a test?
@kubaflo, this issue occurs randomly, so it is not possible to add a reliable test case. I also tried using the UI test agent; however, the scenario could not be reproduced consistently, and the resulting test was not reliable. |
### Description of Change This pull request updates the CI pipeline configuration to introduce and use a new Linux-based pool for running Android tests, replacing the previous macOS-based pools. The main changes are the addition of the `AndroidPoolLinux` parameter and updating the relevant test stage to use this new pool. **Pipeline configuration updates:** * Added a new `AndroidPoolLinux` parameter to `parameters:` in `eng/pipelines/ci.yml` for specifying a Linux pool (`MAUI-DNCENG`) with the `1ESPT-Ubuntu22.04` image for Android test runs. * Updated the `mac_runandroid_tests` stage to use the new `AndroidPoolLinux` pool instead of the previous macOS-based pools, ensuring Android tests run on Linux infrastructure. --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… Change (dotnet#32889) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - When TextColor is not specified and the theme is switched between dark and light (and vice versa), the clearButton color in the Entry does not update correctly. ### Root cause - On iOS, The root cause of the issue is that on iOS, when the text color is null, the clearButton.SetImage method reuses the same image created in the light theme. This breaks iOS’s automatic theme handling, causing the clear button color to remain unchanged. - On Android, When entry.TextColor is null (default), the clear button drawable's color filter was being cleared without applying the appropriate theme color, making the button invisible on dark backgrounds. ### Description of Change - On Android, When TextColor is `null`, now properly retrieves the system's `TextColorPrimary` attribute. Applies the correct color filter to the clear button drawable. Respects the app theme by using the enabled state color from the theme's color state list - On iOS, When TextColor is `null`, now sets `clearButton.TintColor = null` to use system default. Allows the clear button to adapt to the current theme automatically. Ensures tinted clear button image uses correct system colors Validated the behaviour in the following platforms - [x] Android - [x] Windows , - [x] iOS, - [x] MacOS ### Issues Fixed Fixes dotnet#32886 ### Output images Android <table> <tr> <td> Before </td> <td> After </td> </tr> <tr> <td> https://github.com/user-attachments/assets/cde1145b-1440-442f-9cca-f7a2790500ab </td> <td> https://github.com/user-attachments/assets/b072641b-182f-4189-843f-589261da90cc </td> </tr> </table>
…items during navigation
fccff41 to
32e28a7
Compare
I’ve updated the changes based on the review comments. Specifically:
|
…ge on Android when using NavigationPage. (#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes #31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
…ge on Android when using NavigationPage. (#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes #31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
…ge on Android when using NavigationPage. (#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes #31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
…ge on Android when using NavigationPage. (#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes #31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
…ge on Android when using NavigationPage. (dotnet#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes dotnet#31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
…ge on Android when using NavigationPage. (#32311) <!-- 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! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Description: On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly. ### Root Cause: The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened. The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts. ### Fix Description: The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition. This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation. **Note:** This issue occurs in a random manner, so it's not possible to add a test case for it. ### Issues Fixed Fixes #31727 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/9238df6b-d6d5-4e18-8472-d569127c5959">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/ed359046-b420-4bf7-a3cd-8dc1a4a84e6c">| --------- Co-authored-by: Rui Marinho <me@ruimarinho.net> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: rmarinho <1235097+rmarinho@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: TamilarasanSF4853 <tamilarasan.velu@syncfusion.com>
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 46 commits with various improvements, bug fixes, and enhancements. ## Button - [Android] Implemented material3 support for Button by @Dhivya-SF4094 in #33173 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Button](#33172) </details> ## CollectionView - [Android] Fix RemainingItemsThresholdReachedCommand not firing when CollectionView has Header and Footer both defined by @SuthiYuvaraj in #29618 <details> <summary>🔧 Fixes</summary> - [Android : RemainingItemsThresholdReachedCommand not firing when CollectionVew has Header and Footer both defined](#29588) </details> - [iOS/MacCatalyst] Fix CollectionView ScrollTo for horizontal layouts by @Shalini-Ashokan in #33853 <details> <summary>🔧 Fixes</summary> - [[iOS/MacCatalyst] CollectionView ScrollTo does not work with horizontal Layout](#33852) </details> - [iOS & Mac] Fixed IndicatorView Size doesnt update dynamically by @SubhikshaSf4851 in #31129 <details> <summary>🔧 Fixes</summary> - [[iOS, Catalyst] IndicatorView.IndicatorSize does not update dynamically at runtime](#31064) </details> - [Android] Fix for CollectionView Scrolled event is triggered on the initial app load. by @BagavathiPerumal in #33558 <details> <summary>🔧 Fixes</summary> - [[Android] CollectionView Scrolled event is triggered on the initial app load.](#33333) </details> - [iOS, Android] Fix for CollectionView IsEnabled=false allows touch interactions by @praveenkumarkarunanithi in #31403 <details> <summary>🔧 Fixes</summary> - [More issues with CollectionView IsEnabled, InputTransparent, Opacity via Styles and code behind](#19771) </details> - [iOS] Fix VerticalOffset Update When Modifying CollectionView.ItemsSource While Scrolled by @devanathan-vaithiyanathan in #34153 <details> <summary>🔧 Fixes</summary> - [[iOS]VerticalOffset Not Reset to Zero After Clearing ItemSource in CollectionView](#26798) </details> ## DateTimePicker - [Android] Fix DatePicker MinimumDate/MaximumDate not updating dynamically by @HarishwaranVijayakumar in #33687 <details> <summary>🔧 Fixes</summary> - [[regression/8.0.3] [Android] DatePicker control minimum date issue](#19256) - [[Android] DatePicker does not update MinimumDate / MaximumDate in the Popup when set in the viewmodel after first opening](#33583) </details> ## Drawing - Android drawable perf by @albyrock87 in #31567 ## Editor - [Android] Implemented material3 support for Editor by @SyedAbdulAzeemSF4852 in #33478 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for Editor](#33476) </details> ## Entry - [iOS, Mac] Fix for CursorPosition not updating when typing into Entry control by @SyedAbdulAzeemSF4852 in #30505 <details> <summary>🔧 Fixes</summary> - [Entry control CursorPosition does not update on TextChanged event [iOS Maui 8.0.7] ](#20911) - [CursorPosition not calculated correctly on behaviors events for iOS devices](#32483) </details> ## Flyoutpage - [Android, Windows] Fix for FlyoutPage toolbar button not updating on orientation change by @praveenkumarkarunanithi in #31962 <details> <summary>🔧 Fixes</summary> - [Flyout page in Android does not show flyout button (burger) consistently](#24468) </details> - Fix for First Item in CollectionView Overlaps in FlyoutPage.Flyout on iOS by @praveenkumarkarunanithi in #29265 <details> <summary>🔧 Fixes</summary> - [[iOS] CollectionView not rendering first item correctly in FlyoutPage.Flyout](#29170) </details> ## Image - [Android] Fix excessive memory usage for stream and resource-based image loading by @Shalini-Ashokan in #33590 <details> <summary>🔧 Fixes</summary> - [[Android] Unexpected high Bitmap.ByteCount when loading image via ImageSource.FromResource() or ImageSource.FromStream() in .NET MAUI](#33239) </details> - [Android] Fix for Resize method returns an image that has already been disposed by @SyedAbdulAzeemSF4852 in #29964 <details> <summary>🔧 Fixes</summary> - [In GraphicsView, the Resize method returns an image that has already been disposed](#29961) - [IIMage.Resize bugged behaviour](#31103) </details> ## Label - Fixed Label Span font property inheritance when applied via Style by @SubhikshaSf4851 in #34110 <details> <summary>🔧 Fixes</summary> - [`Span` does not inherit text styling from `Label` if that styling is applied using `Style` ](#21326) </details> - [Android] Implemented material3 support for Label by @SyedAbdulAzeemSF4852 in #33599 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for Label](#33598) </details> ## Map - [Android] Fix Circle Stroke color is incorrectly updated as Fill color. by @NirmalKumarYuvaraj in #33643 <details> <summary>🔧 Fixes</summary> - [[Android] Circle Stroke color is incorrectly updated as Fill color.](#33642) </details> ## Mediapicker - [iOS] Fix: invoke MediaPicker completion handler after DismissViewController by @yuriikyry4enko in #34250 <details> <summary>🔧 Fixes</summary> - [[iOS] Media Picker UIImagePickerController closing issue](#21996) </details> ## Navigation - Fix ContentPage memory leak on Android when using NavigationPage modally (fixes #33918) by @brunck in #34117 <details> <summary>🔧 Fixes</summary> - [[Android] Modal TabbedPage whose tabs are NavigationPage(ContentPage) is retained after PopModalAsync()](#33918) </details> ## Picker - [Android] Implement material3 support for TimePicker by @HarishwaranVijayakumar in #33646 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for TimePicker](#33645) </details> - [Android] Implemented Material3 support for Picker by @SyedAbdulAzeemSF4852 in #33668 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Picker](#33665) </details> ## RadioButton - [Android] Implemented material3 support for RadioButton by @SyedAbdulAzeemSF4852 in #33468 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for RadioButton](#33467) </details> ## Setup - Clarify MA003 error message by @jeremy-visionaid in #34067 <details> <summary>🔧 Fixes</summary> - [MA003 false positive with 9.0.21](#26599) </details> ## Shell - [Android] Fix TabBar FlowDirection not updating dynamically by @SubhikshaSf4851 in #33091 <details> <summary>🔧 Fixes</summary> - [[Android, iOS] FlowDirection RTL is not updated dynamically on Shell TabBar](#32993) </details> - [Android] Fix page not disposed on Shell replace navigation by @Vignesh-SF3580 in #33426 <details> <summary>🔧 Fixes</summary> - [[Android] [Shell] replace navigation leaks current page](#25134) </details> - [Android] Fixed Shell flyout does not disable scrolling when FlyoutVerticalScrollMode is set to Disabled by @NanthiniMahalingam in #32734 <details> <summary>🔧 Fixes</summary> - [[Android] Shell.FlyoutVerticalScrollMode="Disabled" does not disable scrolling](#32477) </details> ## Single Project - Fix: Throw a clear error when an SVG lacks dimensions instead of a NullReferenceException by @Shalini-Ashokan in #33194 <details> <summary>🔧 Fixes</summary> - [MAUI Fails To Convert Valid SVG Files Into PNG Files (Object reference not set to an instance of an object)](#32460) </details> ## SwipeView - [iOS] Fix SwipeView stays open on iOS after updating content by @devanathan-vaithiyanathan in #31248 <details> <summary>🔧 Fixes</summary> - [[iOS] - Swipeview with collectionview issue](#19541) </details> ## TabbedPage - [Windows] Fixed IsEnabled Property not works on Tabs by @NirmalKumarYuvaraj in #26728 <details> <summary>🔧 Fixes</summary> - [ShellContent IsEnabledProperty does not work](#5161) - [[Windows] Shell Tab IsEnabled Not Working](#32996) </details> - [Android] Fix NavigationBar overlapping StatusBar when NavigationBar visibility changes by @Vignesh-SF3580 in #33359 <details> <summary>🔧 Fixes</summary> - [[Android] NavigationBar overlaps with StatusBar when mixing HasNavigationBar=true/false in TabbedPage on Android 15 (API 35)](#33340) </details> ## Templates - Fix for unable to open task using keyboard navigation on windows platform by @SuthiYuvaraj in #33647 <details> <summary>🔧 Fixes</summary> - [Unable to open task using keyboard: A11y_.NET maui_User can get all the insights of Dashboard_Keyboard](#30787) </details> ## TitleView - Fix for NavigationPage.TitleView does not expand with host window in iPadOS 26+ by @SuthiYuvaraj in #33088 ## Toolbar - [iOS] Fix toolbar items ignoring BarTextColor on iOS/MacCatalyst 26+ by @Shalini-Ashokan in #34036 <details> <summary>🔧 Fixes</summary> - [[iOS 26] ToolbarItem color with custom BarTextColor not working](#33970) </details> - [Android] Fix for ToolbarItem retaining the icon from the previous page on Android when using NavigationPage. by @BagavathiPerumal in #32311 <details> <summary>🔧 Fixes</summary> - [Toolbaritem keeps the icon of the previous page on Android, using NavigationPage (not shell)](#31727) </details> ## WebView - [Android] Fix WebView in a grid expands beyond it's cell by @devanathan-vaithiyanathan in #32145 <details> <summary>🔧 Fixes</summary> - [Android - WebView in a grid expands beyond it's cell](#32030) </details> ## Xaml - ContentPresenter: Propagate binding context to children with explicit TemplateBinding by @HarishwaranVijayakumar in #30880 <details> <summary>🔧 Fixes</summary> - [Binding context in ContentPresenter](#23797) </details> <details> <summary>🔧 Infrastructure (1)</summary> - [Revert] ContentPresenter: Propagate binding context to children with explicit TemplateBinding by @Ahamed-Ali in #34332 </details> <details> <summary>🧪 Testing (6)</summary> - [Testing] Feature Matrix UITest Cases for Shell Flyout Page by @NafeelaNazhir in #32525 - [Testing] Feature Matrix UITest Cases for Brushes by @LogishaSelvarajSF4525 in #31833 - [Testing] Feature Matrix UITest Cases for BindableLayout by @LogishaSelvarajSF4525 in #33108 - [Android] Add UI tests for Material 3 CheckBox by @HarishwaranVijayakumar in #34126 <details> <summary>🔧 Fixes</summary> - [[Android] Add UI tests for Material 3 CheckBox](#34125) </details> - [Testing] Feature Matrix UITest Cases for Shell Tabbed Page by @NafeelaNazhir in #33159 - [Testing] Fixed Test case failure in PR 34294 - [03/2/2026] Candidate - 1 by @TamilarasanSF4853 in #34334 </details> <details> <summary>📦 Other (2)</summary> - Bumps Syncfusion.Maui.Toolkit dependency to version 1.0.9 by @PaulAndersonS in #34178 - Fix crash when closing Windows based app when using TitleBar by @MFinkBK in #34032 <details> <summary>🔧 Fixes</summary> - [Unhandled exception "Value does not fall within the expected range" when closing Windows app](#32194) </details> </details> **Full Changelog**: main...inflight/candidate
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!
Issue Description:
On Android, when navigating from one page to another (Navigation.PushAsync(...)), the icons from the previous page may persist on the new page. This doesn’t happen all the time (in either debug or release mode); it occurs randomly.
Root Cause:
The issue occurs because of a race condition between asynchronous icon loading and navigation in MAUI's Android toolbar implementation. When navigating from a page with more toolbar items to a page with fewer items (e.g., 3 items → 2 items), the Android MenuItems get reused for performance optimization. However, the icon loading for FontImageSource uses async callbacks that can complete after navigation has already happened.
The async callbacks from the previous page's toolbar items (like "Delete" and "Edit" icons) can fire after the new page has already set up its toolbar items (like "Cancel" and "Validate" icons), causing the old icons to overwrite the new ones. The race condition is timing-dependent and more likely to occur when there are more toolbar items being reused, which explains why the issue manifests primarily when navigating from pages with higher item counts to pages with lower item counts.
Fix Description:
The fix involves implementing a tracking mechanism that associates each MenuItem with its current ToolbarItem to prevent stale async callbacks from overwriting correct icons. A dictionary maps MenuItem IDs to weak references of their currently assigned ToolbarItem. When an async icon loading callback fires, the code validates that the MenuItem is still associated with the ToolbarItem that initiated the icon loading operation. If a different ToolbarItem is now associated with that MenuItem (indicating the MenuItem was reused for a different toolbar item after navigation), the callback is aborted and logged as a detected race condition.
This prevents async callbacks from previous pages from contaminating the icons on the current page. The mapping is cleaned up when MenuItems are disposed to prevent memory leaks. This solution maintains the performance benefits of MenuItem reuse while eliminating the race condition that caused icon persistence across page navigation.
Note: This issue occurs in a random manner, so it's not possible to add a test case for it.
Issues Fixed
Fixes #31727
Tested the behaviour in the following platforms
Output Screenshot
Beforefix-AndroidToolbarItem.mov
Afterfix-AndroidToolbarItem.mov