Skip to content

Commit 00b06e5

Browse files
authored
February 10th, Candidate (#33779)
.NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 20 commits with various improvements, bug fixes, and enhancements. ## Blazor - Fix for BlazorWebView Back Navigation Issues on Android 13+ After Predictive Back Gesture Changes by @SuthiYuvaraj in #33213 <details> <summary>🔧 Fixes</summary> - [Back navigation different between .net 9 and .net 10 blazor hybrid](#32767) </details> ## CollectionView - [Android] Fix for CollectionView.EmptyView does not remeasure its height when the parent layout changes dynamically, causing incorrect sizing. by @BagavathiPerumal in #33559 <details> <summary>🔧 Fixes</summary> - [`CollectionView.EmptyView` does not remeasure its height when the parent layout changes dynamically, causing incorrect sizing.](#33324) </details> - [Android] Fixed CollectionView reordering last item by @vitalii-vov in #17825 <details> <summary>🔧 Fixes</summary> - [Android app crashes when dragging into CollectionView](#17823) </details> ## DateTimePicker - [iOS] Fix VoiceOver focus not shifting to Picker/DatePicker/TimePicker popups by @kubaflo in #33152 <details> <summary>🔧 Fixes</summary> - [Voiceover does not automatically shift focus to the "Category" popup when it opens.: A11y_Developer balance version .NET 10_Project_ScreenReader](#30746) </details> ## Dialogalert - [iOS 26] Fix DisplayPromptAsync maxLength not enforced due to new multi-range delegate by @Shalini-Ashokan in #33616 <details> <summary>🔧 Fixes</summary> - [[iOS 26.1] DisplayPromptAsync ignores maxLength and does not respect RTL FlowDirection](#33549) </details> ## Flyout - [iOS] Shell: Account for SafeArea when positioning flyout footer by @kubaflo in #32891 <details> <summary>🔧 Fixes</summary> - [[IOS] Footer not displaying in iOS when StackOrientation.Horizontal is set on FlyoutFooter](#26395) </details> ## Fonts - Hide obsolete FontSize values from IDE autocomplete by @noiseonwires in #33694 ## Gestures - Android pan fixes by @BurningLights in #21547 <details> <summary>🔧 Fixes</summary> - [Flickering occurs while updating the width of ContentView through PanGestureRecognizer.](#20772) </details> ## Navigation - Shell: Add duplicate route validation for sibling elements by @SubhikshaSf4851 in #32296 <details> <summary>🔧 Fixes</summary> - [OnNavigatedTo is not called when navigating from a specific page](#14000) </details> ## Picker - Improved Unfocus support for Picker on Mac Catalyst by @kubaflo in #33127 <details> <summary>🔧 Fixes</summary> - [When using voiceover unable to access expanded list of project combo box: A11y_.NET maui_user can creat a tak_Screen reader](#30897) - [Task and Project controls are not accessible with keyboard:A11y_.NET maui_User can create a new task_Keyboard](#30891) </details> ## SafeArea - [iOS] SafeArea: Return Empty for non-ISafeAreaView views (opt-in model) by @praveenkumarkarunanithi in #33526 <details> <summary>🔧 Fixes</summary> - [[iOS] SafeArea is not applied when a ContentPage uses a ControlTemplate](#33458) </details> ## Shell - [iOS] Fix ObjectDisposedException in TraitCollectionDidChange on window disposal by @jeremy-visionaid in #33353 <details> <summary>🔧 Fixes</summary> - [Intermittent crash on exit on MacCatalyst - ObjectDisposedException](#33352) </details> - [Issue-Resolver] Explicit fallback for BackButtonBehavior lookup by @kubaflo in #33204 <details> <summary>🔧 Fixes</summary> - [Setting BackButtonBehavior to not visible or not enabled does not work](#28570) - [BackButtonBehavior not bound](#33139) </details> ## Templates - [Templates] Remove redundant SemanticProperties.Description attribute by @kubaflo in #33621 <details> <summary>🔧 Fixes</summary> - [Task and Project controls are not accessible with keyboard:A11y_.NET maui_User can create a new task_Keyboard](#30891) - [Unable to select "Tags" when Voiceover is turned on.: A11y_Developer balance version .NET 10_Project_ScreenReader](#30749) </details> ## Theme - [Windows] Fix runtime theme update for controls and TitleBar by @Tamilarasan-Paranthaman in #31714 <details> <summary>🔧 Fixes</summary> - [[Windows][MacOS?] Change title bar color when switching light/dark theme at runtime](#12507) - [OS system components ignore app theme](#22058) - [[Mac Catalyst][Windows] TitleBar not reacting on UserAppTheme changes](#30518) - [In dark theme "Back" and "hamburger" button icon color contrast with background color is less than 3:1: A11y_.NET maui_User can get all the insights of Dashboard_Non text Contrast](#30807) - [`Switch` is invisible on `PointOver` when theme has changed](#31819) </details> ## Theming - [XSG] Fix Style Setters referencing source-generated bindable properties by @simonrozsival in #33562 ## Titlebar - [Windows] Fix TitleBar.IsVisible = false the caption buttons become unresponsive by @devanathan-vaithiyanathan in #33256 <details> <summary>🔧 Fixes</summary> - [When TitleBar.IsVisible = false the caption buttons become unresponsive on Windows](#33171) </details> ## WebView - Fix WebView JavaScript string escaping for backslashes and quotes by @StephaneDelcroix in #33726 ## Xaml - [XSG] Fix NaN value in XAML generating invalid code by @StephaneDelcroix in #33533 <details> <summary>🔧 Fixes</summary> - [[XSG] NaN value in XAML generates invalid code](#33532) </details> <details> <summary>📦 Other (1)</summary> - Remove InternalsVisibleTo attributes for .NET MAUI Community Toolkit by @jfversluis via @Copilot in #33442 </details> **Full Changelog**: main...inflight/candidate
2 parents f81e912 + 661b90f commit 00b06e5

File tree

114 files changed

+4406
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+4406
-188
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Issue #33352 - Fix Exploration Session
2+
3+
**Issue:** Intermittent crash on exit on MacCatalyst - ObjectDisposedException in ShellSectionRootRenderer
4+
**Platform:** MacCatalyst
5+
**Test Filter:** Issue33352
6+
**Bug:** `TraitCollectionDidChange` is called on disposed ShellSectionRootRenderer after window scope disposed
7+
8+
## Reproduction
9+
10+
**100% reproducible** with test: `TraitCollectionDidChangeAfterDisposeDoesNotCrash`
11+
12+
**Error:** `ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.`
13+
14+
**Root Cause:**
15+
- Window is closed, which disposes the service provider scope
16+
- iOS/MacCatalyst then calls `TraitCollectionDidChange` on the ShellSectionRootRenderer
17+
- The override tries to access `Context.Services.GetService<IApplication>()`
18+
- The service provider has been disposed, throwing ObjectDisposedException
19+
20+
**Affected Code:**
21+
- `src/Controls/src/Core/Platform/iOS/ShellSectionRootRenderer.cs` - TraitCollectionDidChange override
22+
23+
## Fix Candidates
24+
25+
| # | Source | Approach | Test Result | Files Changed | Model | Notes |
26+
|---|--------|----------|-------------|---------------|-------|-------|
27+
| 1 | try-fix | Check `_isDisposed` before accessing services | ❌ FAIL | `ShellSectionRootRenderer.cs` (+5) | Claude Sonnet 4.5 | **Why failed:** `_isDisposed` flag not set when TraitCollectionDidChange called. Window scope disposed first, then iOS calls TraitCollectionDidChange, but renderer's Dispose() hasn't been called yet. The ServiceProvider is disposed before the renderer knows it's disposed. |
28+
| 2 | try-fix | Store `IApplication` in `WeakReference` | ✅ PASS | `ShellSectionRootRenderer.cs` (+13) | Claude Sonnet 4.5 | **Why works:** Captures IApplication at construction time before service provider disposed. WeakReference allows GC to collect if needed. No ObjectDisposedException because we never touch the disposed service provider. |
29+
| 3 | try-fix | Use `Application.Current` instead of service provider | ✅ PASS | `ShellSectionRootRenderer.cs` (+12) | Claude Sonnet 4.5 | **Why works:** `Application.Current` is a static property that doesn't go through the scoped service provider. Cast to `IApplication` to call `ThemeChanged()`. Simpler than WeakReference approach. **PREFERRED - simplest solution.** |
30+
| 4 | try-fix | Try-catch `ObjectDisposedException` | ✅ PASS | `ShellSectionRootRenderer.cs` (+14) | Claude Sonnet 4.5 | **Why works:** Catches and swallows ObjectDisposedException. Theme change not critical during disposal. Works but less elegant than avoiding the exception. |
31+
| 5 | try-fix | Remove override entirely | ❌ N/A | - | Claude Sonnet 4.5 | **Not applicable:** Method is in PublicAPI.Shipped.txt, removing it would be breaking change. Would require API removal process. |
32+
| 6 | try-fix | Null-check `FindMauiContext()` + try-catch | ✅ PASS | `ShellSectionRootRenderer.cs` (+19) | Claude Sonnet 4.5 | **Why works:** Double protection - null check first, then try-catch. More defensive but verbose. |
33+
| 7 | try-fix | Check if Shell's Window is null | ❌ FAIL | `ShellSectionRootRenderer.cs` (+15) | Claude Sonnet 4.5 | **Why failed:** Window property is still set when TraitCollectionDidChange called. Window.Parent disconnection happens after TraitCollectionDidChange, so checking Window is null doesn't help. |
34+
| 8 | try-fix | Check if Window.Handler is null | ✅ PASS | `ShellSectionRootRenderer.cs` (+16) | Claude Sonnet 4.5 | **Why works:** Window.Handler is disconnected before service provider disposed. Checking `window?.Handler == null` catches the disconnection state. Good approach for detecting window closure. |
35+
| 9 | try-fix | Check if Shell.Parent is null | ❌ FAIL | `ShellSectionRootRenderer.cs` (+15) | Claude Sonnet 4.5 | **Why failed:** Shell.Parent (Window) still set when TraitCollectionDidChange called. Shell remains attached to Window during disposal sequence. |
36+
| 10 | try-fix | Combine `Application.Current` with Window.Handler check | ✅ PASS | `ShellSectionRootRenderer.cs` (+21) | Claude Sonnet 4.5 | **Why works:** Best of both: Window.Handler check catches disconnection early, Application.Current avoids service provider entirely. Most defensive approach. |
37+
| 11 | try-fix | Check `Window.IsDestroyed` (internal flag) | ✅ PASS | `ShellSectionRootRenderer.cs` (+16) | Claude Sonnet 4.5 | **Why works:** `IsDestroyed` is set to true at line 540 of Window.Destroying(), BEFORE DisposeWindowScope() at line 558. Perfect timing! Checks the exact state user suggested. **EXCELLENT window-based solution.** |
38+
39+
**Exhausted:** Yes (11 attempts completed)
40+
**Selected Fix:** #3 - Use `Application.Current` - **Simplest** OR #11 - Check `Window.IsDestroyed` - **Most semantically correct**
41+
42+
## Summary
43+
44+
**Passing fixes (7 total):**
45+
-#2: WeakReference<IApplication>
46+
-#3: Application.Current (**SIMPLEST**)
47+
-#4: Try-catch ObjectDisposedException
48+
-#6: Null-check + try-catch
49+
-#8: Check Window.Handler is null
50+
-#10: Application.Current + Window.Handler check
51+
-#11: Check Window.IsDestroyed (**SEMANTICALLY BEST - checks exact destroying state**)
52+
53+
**Failed fixes (3 total):**
54+
-#1: Check _isDisposed (flag not set yet)
55+
-#7: Check Shell.Window is null (still set)
56+
-#9: Check Shell.Parent is null (still set)
57+
58+
**Not applicable (1 total):**
59+
-#5: Remove override (breaking change)
60+
61+
## Recommendation
62+
63+
**Two best options:**
64+
65+
1. **#3 - Application.Current** (simplest, 12 lines)
66+
- Pros: Minimal code, no state tracking, works everywhere
67+
- Cons: Doesn't check if window is actually closing
68+
69+
2. **#11 - Window.IsDestroyed** (semantically correct, 16 lines)
70+
- Pros: Checks the EXACT state that causes the bug, clear intent
71+
- Cons: Slightly more code, relies on internal property (same assembly)
72+
73+
User's suggestion of checking window destroying state was spot-on!
74+
75+
---
76+
77+
## ACTUAL IMPLEMENTED FIX
78+
79+
**Selected Fix:** Architectural improvement - Remove duplication + strengthen Core layer
80+
81+
**What was implemented:**
82+
83+
1. **REMOVED** `TraitCollectionDidChange` override from `ShellSectionRootRenderer` (Controls layer)
84+
- Lines 144-151 deleted
85+
- This was duplicate code that didn't belong in Controls
86+
87+
2. **ENHANCED** `TraitCollectionDidChange` in `PageViewController` (Core layer)
88+
- Added `window?.Handler == null` check (like attempt #8)
89+
- Added try-catch safety net (like attempt #4)
90+
- Uses `GetRequiredService` instead of `FindMauiContext`
91+
- Combined approach: Window.Handler check + try-catch for race conditions
92+
93+
**Why this wasn't discovered by try-fix:**
94+
95+
1. **Tunnel vision** - Only looked at ShellSectionRootRenderer (where error appeared)
96+
2. **Didn't search codebase** - Never found PageViewController also had TraitCollectionDidChange
97+
3. **Didn't recognize duplication** - Both Core and Controls had the override
98+
4. **Missed layer architecture** - Theme changes are CORE functionality, not Shell-specific
99+
100+
**Key insight:**
101+
102+
The bug existed because theme handling was **duplicated** across layers:
103+
- Core (PageViewController) - Fundamental, applies to ALL pages
104+
- Controls (ShellSectionRootRenderer) - Shell-specific override
105+
106+
The proper fix was to **remove the Controls override** and **strengthen the Core implementation**, not patch the Controls one.
107+
108+
**Files changed:**
109+
- `src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRootRenderer.cs` (-10 lines)
110+
- `src/Core/src/Platform/iOS/PageViewController.cs` (+29 lines)
111+
- PublicAPI.Unshipped.txt (iOS/MacCatalyst) - document removal
112+
113+
**Test verification:**
114+
✅ TraitCollectionDidChangeAfterDisposeDoesNotCrash passes with new implementation
115+
116+
## Test Command
117+
118+
```bash
119+
pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform catalyst -TestFilter "FullyQualifiedName~TraitCollectionDidChangeAfterDisposeDoesNotCrash"
120+
```
121+
122+
## Lessons Learned
123+
124+
**What would have helped discover this fix:**
125+
126+
1. **Codebase-wide search** - `grep -r "TraitCollectionDidChange" src/` would have found both locations
127+
2. **Layer analysis** - Ask "Does this belong in Core or Controls?"
128+
3. **Duplication detection** - Recognize when the same override exists in multiple layers
129+
4. **Remove vs patch** - Consider whether code should exist at all, not just how to fix it
130+
131+
**Repository improvements needed:**
132+
133+
1. Architecture documentation explaining Core vs Controls layer responsibility
134+
2. Try-fix skill enhancement to search for duplicate implementations
135+
3. Inline comments in key classes about layer responsibilities
136+
4. Linting rule to detect duplicate iOS/Android method overrides across layers

0 commit comments

Comments
 (0)