-
Notifications
You must be signed in to change notification settings - Fork 1.9k
February 21st, 2026 Candidate Branch #34173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
76b01e4
Fix Glide IllegalArgumentException in PlatformInterop for destroyed a…
Copilot fbdaec5
[iOS] Fix MauiCALayer and StaticCAShapeLayer crash on finalizer threa…
pshoey 322f69e
[Android] Fixed duplicate title icon when setting TitleIconImageSourc…
SubhikshaSf4851 6b07ab0
[Android] Fix Numeric Entry not accepting the appropriate Decimal Sep…
devanathan-vaithiyanathan fd276c6
[Android & iOS] Entry/Editor: Dismiss keyboard when control becomes i…
prakashKannanSf3972 c57f77c
[Android] Shell: Fix OnBackButtonPressed not firing for navigation ba…
kubaflo 061a200
[Android] Fixed PointerGestureRecognizer not triggering PointerMoved …
KarthikRajaKalaimani 5ad59f0
Fixed Editor vertical text alignment not working after toggling IsVis…
NanthiniMahalingam eee0535
[iOS] Fixed Shell Navigating event showing same current and target va…
Vignesh-SF3580 757447a
Fix ImageButton not rendering correctly based on its bounds (#28309)
Shalini-Ashokan 2133acd
[Android] Fixed issue where group Header/Footer template was applied …
Tamilarasan-Paranthaman 116bf55
[Android] Fixed CollectionView items do not reorder correctly when us…
NanthiniMahalingam 7b77352
[Android] Fix for incorrect scroll position when using ScrollTo with …
SyedAbdulAzeemSF4852 d8d5cb7
Fixed the crash on iOS when setting HeightRequest on WebView inside a…
Ahamed-Ali 6ed4d42
[iOS, macOS] Fixed Shell Flyout Icon is always black in iOS 26 (#32997)
Dhivya-SF4094 b50b700
Fix Incorrect Scrolling Behavior in CollectionView ScrollTo Method Us…
Shalini-Ashokan 15e8a3c
[Android] Fixed TransformProperties issue when a wrapper view is pres…
Ahamed-Ali fb104dc
[Android] Fix System.IndexOutOfRangeException when scrolling Collecti…
devanathan-vaithiyanathan 746d160
[Android] Fix VerticalOffset Update When Modifying CollectionView.Ite…
devanathan-vaithiyanathan 9291950
[Testing] Fix for enable uitests ios26 (#33686)
TamilarasanSF4853 4058e34
[Testing] Fixed Test case failure in PR 34173 - [02/21/2026] Candidat…
TamilarasanSF4853 66a814e
[Android] Fix PointerMoved and PointerReleased not firing in PointerG…
KarthikRajaKalaimani 4b17d62
[Testing] Fixed Test case failure in PR 34173 - [02/21/2026] Candidat…
TamilarasanSF4853 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,261 @@ | ||
| # PR Review: #31487 - [Android] Fixed duplicate title icon when setting TitleIconImageSource Multiple times | ||
|
|
||
| **Date:** 2026-01-08 | **Issue:** [#31445](https://github.com/dotnet/maui/issues/31445) | **PR:** [#31487](https://github.com/dotnet/maui/pull/31487) | ||
|
|
||
| ## ✅ Final Recommendation: APPROVE | ||
|
|
||
| | Phase | Status | | ||
| |-------|--------| | ||
| | Pre-Flight | ✅ COMPLETE | | ||
| | 🧪 Tests | ✅ COMPLETE | | ||
| | 🚦 Gate | ✅ PASSED | | ||
| | 🔧 Fix | ✅ COMPLETE | | ||
| | 📋 Report | ✅ COMPLETE | | ||
|
|
||
| --- | ||
|
|
||
| <details> | ||
| <summary><strong>📋 Issue Summary</strong></summary> | ||
|
|
||
| On Android, calling `NavigationPage.SetTitleIconImageSource(page, "image.png")` more than once for the same page results in the icon being rendered multiple times in the navigation bar. | ||
|
|
||
| **Steps to Reproduce:** | ||
| 1. Launch app on Android | ||
| 2. Tap "Set TitleIconImageSource" once: icon appears | ||
| 3. Tap it again: a second identical icon appears | ||
|
|
||
| **Expected:** Single toolbar icon regardless of how many times SetTitleIconImageSource is called. | ||
|
|
||
| **Actual:** Each repeated call adds an additional duplicate icon. | ||
|
|
||
| **Platforms Affected:** | ||
| - [ ] iOS | ||
| - [x] Android | ||
| - [ ] Windows | ||
| - [ ] MacCatalyst | ||
|
|
||
| **Version:** 9.0.100 SR10 | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>📁 Files Changed</strong></summary> | ||
|
|
||
| | File | Type | Changes | | ||
| |------|------|---------| | ||
| | `src/Controls/src/Core/Platform/Android/Extensions/ToolbarExtensions.cs` | Fix | +17/-6 | | ||
| | `src/Controls/tests/TestCases.HostApp/Issues/Issue31445.cs` | Test | +38 | | ||
| | `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31445.cs` | Test | +23 | | ||
| | `snapshots/android/Issue31445DuplicateTitleIconDoesNotAppear.png` | Snapshot | binary | | ||
| | `snapshots/mac/Issue31445DuplicateTitleIconDoesNotAppear.png` | Snapshot | binary | | ||
| | `snapshots/windows/Issue31445DuplicateTitleIconDoesNotAppear.png` | Snapshot | binary | | ||
| | `snapshots/ios/Issue31445DuplicateTitleIconDoesNotAppear.png` | Snapshot | binary | | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>💬 PR Discussion Summary</strong></summary> | ||
|
|
||
| **Key Comments:** | ||
| - Issue verified by LogishaSelvarajSF4525 on MAUI 9.0.0 & 9.0.100 | ||
| - PR triggered UI tests by jsuarezruiz | ||
| - PureWeen requested rebase | ||
|
|
||
| **Reviewer Feedback:** | ||
| - Copilot review: Suggested testing with different image sources or rapid succession to validate fix better | ||
|
|
||
| **Disagreements to Investigate:** | ||
| | File:Line | Reviewer Says | Author Says | Status | | ||
| |-----------|---------------|-------------|--------| | ||
| | Issue31445.cs:31 | Test with different images or rapid calls | N/A | ⚠️ INVESTIGATE | | ||
|
|
||
| **Author Uncertainty:** | ||
| - None noted | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>🧪 Tests</strong></summary> | ||
|
|
||
| **Status**: ✅ COMPLETE | ||
|
|
||
| - [x] PR includes UI tests | ||
| - [x] Tests reproduce the issue | ||
| - [x] Tests follow naming convention (`Issue31445`) | ||
|
|
||
| **Test Files:** | ||
| - HostApp: `src/Controls/tests/TestCases.HostApp/Issues/Issue31445.cs` | ||
| - NUnit: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31445.cs` | ||
|
|
||
| **Test Behavior:** | ||
| - Uses snapshot verification (`VerifyScreenshot()`) | ||
| - Navigates to test page, taps button to trigger duplicate icon scenario | ||
| - Verified to compile successfully | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>🚦 Gate - Test Verification</strong></summary> | ||
|
|
||
| **Status**: ✅ PASSED | ||
|
|
||
| - [x] Tests FAIL without fix (bug reproduced - duplicate icons appeared) | ||
| - [x] Tests PASS with fix (single icon as expected) | ||
|
|
||
| **Result:** PASSED ✅ | ||
|
|
||
| **Verification Details:** | ||
| - Platform: Android (emulator-5554) | ||
| - Without fix: Test FAILED (screenshot mismatch - duplicate icons) | ||
| - With fix: Test PASSED (single icon verified) | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>🔧 Fix Candidates</strong></summary> | ||
|
|
||
| **Status**: ✅ COMPLETE | ||
|
|
||
| | # | Source | Approach | Test Result | Files Changed | Model | Notes | | ||
| |---|--------|----------|-------------|---------------|-------|-------| | ||
| | 1 | try-fix | Check for existing icon view at position 0, reuse if exists, only create new if needed | ✅ PASS | `ToolbarExtensions.cs` (+7) | Opus 4.5 | Works! Independently arrived at same solution logic as PR | | ||
| | 2 | try-fix | Dedupe defensively by scanning all toolbar children, keep first `ToolbarTitleIconImageView`, remove extras; then reuse/create | ✅ PASS | `ToolbarExtensions.cs` (+22/-5) | GPT 5.2 | More robust if child ordering changes or duplicates already exist | | ||
| | 3 | try-fix | Use `FindViewWithTag` to uniquely identify/retrieve the MAUI title icon | ✅ PASS | `ToolbarExtensions.cs` (+20/-6) | Gemini 2.0 Flash | Explicit identification; avoids index assumptions and iteration; most robust against external view insertions | | ||
| | PR | PR #31487 | Check for existing ToolbarTitleIconImageView before adding new one | ✅ PASS (Gate) | `ToolbarExtensions.cs` (+17/-6) | Author | Original PR - validated by Gate | | ||
|
|
||
| **Exhausted:** Yes (3 passing alternatives found) | ||
|
|
||
| **Selected Fix:** PR's fix - It’s simplest and sufficient. | ||
| - #3 (Tag) is the most "correct" for robustness but adds Tag management overhead. | ||
| - #2 (Dedupe) is good for cleanup. | ||
| - PR/#1 (Index 0) are standard for this codebase's patterns. | ||
|
|
||
| **Comparison Notes:** | ||
| - PR/try-fix #1 rely on `GetChildAt(0)` being the title icon view when present | ||
| - try-fix #2 is more defensive: it collapses existing duplicates regardless of child index and then reuses/creates as needed | ||
| - try-fix #3 uses explicit tagging: precise but introduces new state (Tag) to manage | ||
|
|
||
| </details> | ||
|
|
||
| --- | ||
|
|
||
| **Next Step:** Propose Alternative Fix #2 (Dedupe & Scan) to Author for Discussion | ||
|
|
||
| --- | ||
|
|
||
| ## 💬 Draft Comment for Author | ||
|
|
||
| Hi @PureWeen, | ||
|
|
||
| Reviewing the fix in this PR, it works correctly for the reported issue and tests pass. | ||
|
|
||
| I explored a couple of alternative approaches and found one that might offer slightly better robustness against edge cases, which I wanted to run by you: | ||
|
|
||
| **Alternative: Dedupe & Scan** | ||
| Instead of just checking index 0, we could scan all children of the toolbar to find any `ToolbarTitleIconImageView` instances. | ||
|
|
||
| ```csharp | ||
| // Scan all children to find existing title icons | ||
| ToolbarTitleIconImageView? titleIcon = null; | ||
| for (int i = 0; i < nativeToolbar.ChildCount; i++) | ||
| { | ||
| var child = nativeToolbar.GetChildAt(i); | ||
| if (child is ToolbarTitleIconImageView icon) | ||
| { | ||
| if (titleIcon == null) | ||
| titleIcon = icon; // Keep the first one found | ||
| else | ||
| nativeToolbar.RemoveView(icon); // Remove any extras (self-healing) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Why consider this?** | ||
| 1. **Robustness against Injection:** If another library inserts a view at index 0 (e.g., search bar), the current PR fix (checking only index 0) would fail to see the existing icon and create a duplicate. | ||
| 2. **Self-Healing:** If the toolbar is already in a bad state (multiple icons from previous bugs), this approach cleans them up. | ||
|
|
||
| **Trade-off:** | ||
| It involves a loop, so O(N) instead of O(1), but for a toolbar with very few items, this is negligible. | ||
|
|
||
| Do you think the added robustness is worth the change, or should we stick to the simpler Index 0 check (current PR) which matches the existing removal logic? | ||
|
|
||
| --- | ||
|
|
||
| ## 📋 Final Report | ||
|
|
||
| ### Summary | ||
|
|
||
| PR #31487 correctly fixes the duplicate title icon issue on Android. The fix checks for an existing `ToolbarTitleIconImageView` at position 0 before creating a new one, preventing duplicate icons when `SetTitleIconImageSource` is called multiple times. | ||
|
|
||
| ### Root Cause | ||
|
|
||
| The original `UpdateTitleIcon` method always created a new `ToolbarTitleIconImageView` and added it to position 0, without checking if one already existed. This caused duplicate icons when the method was called repeatedly. | ||
|
|
||
| ### Validation | ||
|
|
||
| | Check | Result | | ||
| |-------|--------| | ||
| | Tests reproduce bug | ✅ Test fails without fix (duplicate icons) | | ||
| | Tests pass with fix | ✅ Test passes with fix (single icon) | | ||
| | Independent fix analysis | ✅ try-fix arrived at same solution | | ||
| | Code quality | ✅ Clean, minimal change | | ||
|
|
||
| ### Regression Analysis | ||
|
|
||
| <details> | ||
| <summary><strong>📜 Git History Analysis</strong></summary> | ||
|
|
||
| **Original Implementation:** `e2f3aaa222` (Oct 2021) by Shane Neuville | ||
| - Part of "[Android] ToolbarHandler and fixes for various page nesting scenarios (#2781)" | ||
| - The bug has existed since the original implementation - it was never designed to handle repeated calls | ||
|
|
||
| **Key Finding:** The original code had a check for removing an existing icon when source is null/empty: | ||
| ```csharp | ||
| if (nativeToolbar.GetChildAt(0) is ToolbarTitleIconImageView existingImageView) | ||
| nativeToolbar.RemoveView(existingImageView); | ||
| ``` | ||
| But this check was **only in the removal path**, not in the creation path. The fix extends this pattern to also check before adding. | ||
|
|
||
| **Related Toolbar Issues in This File:** | ||
| | Commit | Issue | Description | | ||
| |--------|-------|-------------| | ||
| | `a93e88c3de` | #7823 | Fix toolbar item icon not removed when navigating | | ||
| | `c04b7d79cc` | #19673 | Fixed android toolbar icon change | | ||
| | `158ed8b4f1` | #28767 | Removing outdated menu items after activity switch | | ||
|
|
||
| **Pattern:** Multiple fixes in this file address issues where Android toolbar state isn't properly cleaned up or reused. This PR follows the same pattern. | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>🔄 Platform Comparison</strong></summary> | ||
|
|
||
| | Platform | TitleIcon Implementation | Duplicate Prevention | | ||
| |----------|-------------------------|---------------------| | ||
| | **Android** | Creates `ToolbarTitleIconImageView`, adds to position 0 | ❌ Was missing (now fixed by PR) | | ||
| | **Windows** | Sets `TitleIconImageSource` property directly | ✅ Property-based, no duplicates possible | | ||
| | **iOS** | Uses `NavigationRenderer` with property binding | ✅ Property-based approach | | ||
|
|
||
| **Why Android was vulnerable:** Android uses a view-based approach (adding/removing child views) while other platforms use property-based approaches. View management requires explicit duplicate checks. | ||
|
|
||
| </details> | ||
|
|
||
| <details> | ||
| <summary><strong>⚠️ Risk Assessment</strong></summary> | ||
|
|
||
| **Regression Risk: LOW** | ||
|
|
||
| 1. **Minimal change** - Only modifies the creation logic, doesn't change removal | ||
| 2. **Consistent pattern** - Uses same `GetChildAt(0)` check that already existed for removal | ||
| 3. **Well-tested** - UI test verifies the specific scenario | ||
| 4. **No side effects** - Reusing existing view is safe; `SetImageDrawable` handles updates | ||
|
|
||
| **Potential Edge Cases (from Copilot review suggestion):** | ||
| - Setting different image sources rapidly → Should work fine, image is updated on existing view | ||
| - Setting same source multiple times → Explicitly tested, works correctly | ||
|
|
||
| </details> | ||
|
|
||
| ### Recommendation | ||
|
|
||
| **✅ APPROVE** - The PR's approach is correct and validated by independent analysis. The fix is minimal, focused, and addresses the root cause. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a GitHub issue link reference to the comment explaining the iOS 26 tint color inheritance change. This would help track when the workaround can be removed if Apple fixes the issue in a future iOS release.