[Android] Implement material3 support for TimePicker#33646
[Android] Implement material3 support for TimePicker#33646jfversluis merged 11 commits intodotnet:inflight/currentfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Implements an Android Material3-based TimePicker pipeline, introducing a new material time picker control and handler, and wiring them into the handler registration when Material3 is enabled.
Changes:
- Added
MauiMaterialTimePicker, a Material3-themedTextInputEditTextwrapper that exposesShowPicker/HidePickeractions and usesMauiMaterialContextThemeWrapperfor control-level theming. - Introduced
TimePickerHandler2on Android, which usesMaterialTimePicker(M3 dialog) and mapsITimePickerproperties (Time,Format,TextColor,IsOpen, etc.) to the new material platform view. - Updated handler registration and Android
TimePickerExtensionsso thatTimePickerHandler2andMauiMaterialTimePickerare used whenRuntimeFeature.IsMaterial3Enabled, while preserving the existing handler and extension behavior otherwise.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Core/src/Platform/Android/TimePickerExtensions.cs |
Adds UpdateFormat, UpdateTime, SetTime, and UpdateTextColor overloads for MauiMaterialTimePicker, mirroring the existing MauiTimePicker behavior for formatting and text color. |
src/Core/src/Platform/Android/MauiMaterialTimePicker.cs |
Introduces the internal MauiMaterialTimePicker control based on TextInputEditText, wired to open the picker via ShowPicker and configured with Material3/themed background and picker initialization. |
src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs |
Defines TimePickerHandler2, a Material3-specific Android handler that creates and manages a MaterialTimePicker dialog, maps ITimePicker properties, and synchronizes IsOpen with dialog visibility via listeners. |
src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs |
Adjusts AddControlsHandlers to register TimePickerHandler2 for TimePicker on Android when RuntimeFeature.IsMaterial3Enabled, and otherwise keep using the existing TimePickerHandler. |
| internal partial class TimePickerHandler2 : ViewHandler<ITimePicker, MauiMaterialTimePicker> | ||
| { | ||
| internal MaterialTimePicker? _dialog; | ||
| internal bool _isUpdatingIsOpen; | ||
| internal MaterialTimePickerPositiveButtonClickListener? _positiveButtonClickListener; | ||
| internal MaterialTimePickerDismissListener? _dismissListener; | ||
|
|
||
| public static PropertyMapper<ITimePicker, TimePickerHandler2> Mapper = | ||
| new(ViewMapper) | ||
| { | ||
| [nameof(ITimePicker.Background)] = MapBackground, | ||
| [nameof(ITimePicker.CharacterSpacing)] = MapCharacterSpacing, | ||
| [nameof(ITimePicker.Font)] = MapFont, | ||
| [nameof(ITimePicker.Format)] = MapFormat, | ||
| [nameof(ITimePicker.TextColor)] = MapTextColor, | ||
| [nameof(ITimePicker.Time)] = MapTime, | ||
| [nameof(ITimePicker.IsOpen)] = MapIsOpen, | ||
| }; | ||
|
|
||
| public static CommandMapper<ITimePicker, TimePickerHandler2> CommandMapper = new(ViewCommandMapper) | ||
| { | ||
| }; | ||
|
|
||
| public TimePickerHandler2() : base(Mapper, CommandMapper) |
There was a problem hiding this comment.
This new Material3-specific handler (TimePickerHandler2) introduces a separate code path for Android time pickers, but there are no corresponding device/unit tests exercising its behavior (e.g., IsOpen mapping, time formatting, 24-hour vs 12-hour modes, and text color updates), while the existing TimePickerHandler is covered by tests in src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests*.cs. To prevent regressions and ensure the Material3 path matches the legacy handler’s behavior, consider adding Android device tests that force RuntimeFeature.IsMaterial3Enabled and verify TimePickerHandler2 correctly handles these scenarios.
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — Remove dispose call ·
|
| File:Line | Reviewer Says | Status |
|---|---|---|
TimePickerHandler2.Android.cs:63 |
_positiveButtonClickListener and _dismissListener (both Java.Lang.Object) are not disposed before nulling - potential native memory UNRESOLVED |
leak |
TimePickerHandler2.Android.cs:34 |
No device/unit tests for TimePickerHandler2 - existing TimePickerHandler tests not extended to cover Material3 code UNRESOLVED |
path |
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #33646 | New TimePickerHandler2 + MauiMaterialTimePicker with Material3 dialog; conditional handler PENDING (Gate) |
4 files (+390/-1) | Original PR | registration |
🚦 Gate — Test Verification
📝 Review Session — Remove dispose call · 6d1e724
** FAILEDResult:**
Platform: android
Mode: No-tests check
Findings
- No test files found for issue Implement Material3 support for TimePicker #33645 or
TimePickerHandler2- Checked
src/Controls/tests/TestCases.HostApp/ noIssue33645*filesIssues/ - Checked
src/Core/tests/DeviceTests/Handlers/ no tests forTimePickerHandler2TimePicker/ - PR added 390 lines of new implementation code with 0 test lines
- Checked
Existing test coverage for comparison
The existing (non-Material3) TimePickerHandler is covered by:
src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.cssrc/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.Android.cs
No equivalent tests exist for TimePickerHandler2 (Material3 path).
tests must be added before this PR can proceed.FAILED Gate
🔧 Fix — Analysis & Comparison
📝 Review Session — Remove dispose call · 6d1e724
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #33646 | New TimePickerHandler2 + MauiMaterialTimePicker with Material3 MaterialTimePicker dialog; conditional registration via `RuntimeFeature. NOT RUN (Gate failed) |
4 files (+390/-1) | Original not validated | PR IsMaterial3Enabled` |
Exhausted: N/ Gate failed, try-fix phase skipped per workflow rulesA
Selected Fix: N/ cannot select until tests exist and Gate passesA
📋 Report — Final Recommendation
📝 Review Session — Remove dispose call · 6d1e724
Final Recommendation: REQUEST CHANGES##
Summary
PR #33646 implements Material3 support for the Android TimePicker by adding a new TimePickerHandler2 class backed by Google's MaterialTimePicker dialog and a MauiMaterialTimePicker platform control. The implementation approach is well-structured and follows existing MAUI patterns (MauiMaterialButton precedent), and the conditional handler registration via RuntimeFeature.IsMaterial3Enabled is architecturally sound.
However, the PR cannot be approved in its current state due to missing tests and a native resource leak in DisconnectHandler.
Gate Result
** No tests exist for TimePickerHandler2. The PR adds ~390 lines of new handler and platform control code with zero corresponding test files. The existing TimePickerHandler has device tests in src/Core/tests/DeviceTests/Handlers/TimePicker/ that should be mirrored for the Material3 path.FAILED**
Issues Found
TimePickerHandler2 has no device tests. The following scenarios from the existing TimePickerHandlerTests.Android.cs should be covered:
IsOpenmapping (show/hide dialog via property)- Time initialization and format (12h vs 24h)
UpdateTextColor/ null text color handlingUpdateFontandUpdateCharacterSpacing
Suggested approach: Add TimePickerHandler2Tests.Android.cs in src/Core/tests/DeviceTests/Handlers/TimePicker/ that forces RuntimeFeature.IsMaterial3Enabled for the test scope and validates the same scenarios covered for TimePickerHandler.
Additionally, a UI test (Issue33645) in TestCases.HostApp with a corresponding TestCases.Shared.Tests NUnit test would demonstrate the end-to-end Material3 appearance on Android.
In DisconnectHandler, _positiveButtonClickListener and _dismissListener are set to null after RemoveListeners(), but their Dispose() method is never called. Since both implement Java.Lang.Object, failing to dispose them keeps the underlying Java heap objects alive until the GC finalizes them, which can cause native memory pressure.
Fix:
// In DisconnectHandler, after RemoveListeners():
_positiveButtonClickListener?.Dispose();
_positiveButtonClickListener = null;
_dismissListener?.Dispose();
_dismissListener = null;// Current (redundant):
if (handler.IsConnected() && handler is TimePickerHandler2 timePickerHandler && !timePickerHandler._isUpdatingIsOpen)
// Simplified (handler is already TimePickerHandler2):
if (handler.IsConnected() && !handler._isUpdatingIsOpen)What's Good
- The
MaterialTimePickerdialog integration (usingFragmentManager,AddOnPositiveButtonClickListener,AddOnDismissListener) is solid and follows Android best practices WeakReference<TimePickerHandler2>in listener classes correctly prevents reference cycles- Guard against duplicate dialogs (
_dialog.IsVisible || _dialog.IsAdded) is correct - The
Use24HourViewlogic correctly handles both device locale and explicit format string _isUpdatingIsOpenflag prevents feedback loops when updatingVirtualView.IsOpen- The conditional handler registration in
AppHostBuilderExtensions.csis clean and correct
Requested Changes
- [Required] Add device tests for
TimePickerHandler2insrc/Core/tests/DeviceTests/Handlers/TimePicker/ - [Required] Add
Dispose()calls on_positiveButtonClickListenerand_dismissListenerinDisconnectHandler - [Optional] Add a UI test (
Issue33645) inTestCases.HostAppdemonstrating Material3 TimePicker appearance - [Optional] Simplify redundant
handler is TimePickerHandler2 timePickerHandlercast inMapIsOpen
📋 Expand PR Finalization Review
Title: ✅ Good
Current: [Android] Implement material3 support for TimePicker
Description: ✅ Good
Description needs updates. See details below.
Missing Elements:
**
- ❌ Missing NOTE block - The required PR testing note is absent. Must be prepended.
- ❌ Missing
// TODO: Material3: Make it public in .NET 11context - The description doesn't mention thatTimePickerHandler2andMauiMaterialTimePickerare intentionally internal and will be made public in .NET 11 (as noted by theTODOcomments in code). - ❌ No "Breaking Changes" section - Should explicitly state "None".
- ❌ No mention of
MapIsOpen/_isUpdatingIsOpenre-entrancy guard - This is a non-obvious design decision worth documenting.
Verdict: Good description with accurate content. Add NOTE block and minor clarifications; do not replace wholesale.
Actions needed:
- Prepend NOTE block
- Add "Breaking Changes: None" section
- Clarify the internal/TODO-public-in-.NET-11 intent
Phase 2: Code Review
See code-review.md for detailed findings.
Summary:
- 🔴 1 critical issue (redundant pattern match, functionally harmless but misleading)
- 🟡 3 suggestions (listener disposal, missing newlines at EOF, state inconsistency edge case)
- ❌ 0 blocking issues, but existing review comments (listener disposal, no tests) remain unaddressed
Action Summary
| Item | Priority | Action |
|---|---|---|
| Add NOTE block to description | High | Prepend to description |
| Dispose listeners in DisconnectHandler | Medium | Address existing reviewer comment |
Fix redundant is check in MapIsOpen |
Low | Cosmetic fix |
| Add tests for TimePickerHandler2 | Medium | Address existing reviewer comment |
| Add newlines at EOF | Low | TimePickerHandler2.Android.cs and TimePickerExtensions.cs |
| Add "Breaking Changes: None" | Low | Add to description |
✨ 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!
Description of Change
This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of TimePickerHandler2 and MauiMaterialTimePicker when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise.
Note:
TimePickerHandler2andMauiMaterialTimePickerare intentionallyinternalin this PR. They will be madepublicin .NET 11 once the Material3 migration is complete. SeeTODO: Material3: Make it public in .NET 11comments throughout the code.
Material3 Time Picker Integration
- Added new
TimePickerHandler2class for Android, implementing a Material3-style time picker dialog usingGoogle.Android.Material.TimePicker.MaterialTimePicker(dial/clock face mode). Handles property mappings for time, format, background, text color, font, and open/close state. Uses_isUpdatingIsOpenguard to prevent re-entrantIsOpenupdates. (src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs) - Introduced
MauiMaterialTimePickercontrol for Android, extendingTextInputEditTextwith Material3 context theming viaMauiMaterialContextThemeWrapper. ExposesShowPicker/HidePickeraction delegates invoked from the handler. (src/Core/src/Platform/Android/MauiMaterialTimePicker.cs)
Handler Registration Updates
- Modified
AddControlsHandlersto registerTimePickerHandler2for Android whenRuntimeFeature.IsMaterial3Enabledis true, falling back toTimePickerHandlerotherwise. On all other platforms,TimePickerHandlercontinues to be used unconditionally. (src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs)
Platform Extension Methods
- Added
UpdateFormat,UpdateTime, andUpdateTextColorextension methods forMauiMaterialTimePicker, mirroring the existing methods forMauiTimePickerand ensuring correct formatting and appearance with Material3 theming. (src/Core/src/Platform/Android/TimePickerExtensions.cs)
Material Design Spec - TimePicker
Breaking Changes
None
Issues Fixed
Fixes #33645
Output
| Material 2 | Material 3 |
|---|---|
![]() |
![]() |
Code Review: ⚠️ Issues Found
Code Review: PR #33646 — [Android] Implement Material3 support for TimePicker
🔴 Critical Issues
None that block merge.
🟡 Suggestions
1. Redundant type check in MapIsOpen
File: src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs
Problem: handler is TimePickerHandler2 timePickerHandler is always true because the method signature already types the parameter as TimePickerHandler2. This creates a misleading pattern suggesting the cast could fail.
// Current (line ~170)
public static void MapIsOpen(TimePickerHandler2 handler, ITimePicker picker)
{
if (handler.IsConnected() && handler is TimePickerHandler2 timePickerHandler && !timePickerHandler._isUpdatingIsOpen)
{
if (picker.IsOpen)
timePickerHandler.ShowPickerDialog();
else
timePickerHandler.HidePickerDialog();
}
}Recommended fix:
public static void MapIsOpen(TimePickerHandler2 handler, ITimePicker picker)
{
if (handler.IsConnected() && !handler._isUpdatingIsOpen)
{
if (picker.IsOpen)
handler.ShowPickerDialog();
else
handler.HidePickerDialog();
}
}Note: This contrasts with the existing TimePickerHandler.MapIsOpen which does the is check because it uses ITimePickerHandler as the parameter type.
2. Listener instances not disposed in DisconnectHandler
File: src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs
Problem: MaterialTimePickerPositiveButtonClickListener and MaterialTimePickerDismissListener both derive from Java.Lang.Object. In DisconnectHandler, after RemoveListeners(), the listener instances are set to null but never Dispose()d. Other Android handler code in this repo explicitly disposes Java.Lang.Object derivatives to release native references.
(This was also flagged by the existing copilot-pull-request-reviewer comment.)
Current code (lines ~57–65):
_positiveButtonClickListener = null;
_dismissListener = null;Recommended fix:
_positiveButtonClickListener?.Dispose();
_positiveButtonClickListener = null;
_dismissListener?.Dispose();
_dismissListener = null;3. UpdateIsOpenState(true) called even when dialog creation returns null
File: src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs
Problem: CreateTimePickerDialog returns MaterialTimePicker? (nullable). In ShowPickerDialog(TimeSpan? time), the null-conditional call _dialog?.Show(...) means that if _dialog is null, nothing is actually shown — but UpdateIsOpenState(true) is still called unconditionally, leaving VirtualView.IsOpen = true even though no dialog appeared.
// Current
_dialog = CreateTimePickerDialog(hour, minute);
_dialog?.Show(fragmentManager, "MaterialTimePicker");
UpdateIsOpenState(true); // ← called even if _dialog is nullRecommended fix:
_dialog = CreateTimePickerDialog(hour, minute);
if (_dialog is null)
return;
_dialog.Show(fragmentManager, "MaterialTimePicker");
UpdateIsOpenState(true);4. Missing newline at end of files
Files:
src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs—\ No newline at end of filesrc/Core/src/Platform/Android/TimePickerExtensions.cs—\ No newline at end of file
Both files are missing a trailing newline. Most files in this repo end with a newline; the diff shows \ No newline at end of file for both. Add a newline to match repo conventions.
5. Secondary constructors in MauiMaterialTimePicker don't apply theme wrapper
File: src/Core/src/Platform/Android/MauiMaterialTimePicker.cs
Problem: The primary constructor wraps the context with MauiMaterialContextThemeWrapper.Create(context), but the secondary constructors ((context, attrs) and (context, attrs, defStyleAttr)) pass context directly without wrapping. Compare with MauiMaterialButton, which applies the wrapper in all constructors.
// Current
public MauiMaterialTimePicker(Context context) : base(MauiMaterialContextThemeWrapper.Create(context))
public MauiMaterialTimePicker(Context context, IAttributeSet? attrs) : base(context, attrs) // no wrap
public MauiMaterialTimePicker(Context context, IAttributeSet? attrs, int defStyleAttr) : base(context, attrs, defStyleAttr) // no wrapRecommended: If the control can be inflated from XML (unlikely but possible), apply the wrapper consistently. If the secondary constructors are purely for JNI/runtime use, this is lower priority but should be documented.
❌ Unaddressed Existing Review Comments
Two review comments from copilot-pull-request-reviewer posted 2026-01-27 are not yet resolved:
- [PRRC_kwDOD6PVWM6iw2g-] — Dispose
_positiveButtonClickListenerand_dismissListenerinDisconnectHandler(see Suggestion Update README.md #2 above) - [PRRC_kwDOD6PVWM6iw2hP] — No device tests for
TimePickerHandler2; the existing handler is covered by tests insrc/Core/tests/DeviceTests/Handlers/TimePicker/. Material3 path should also be tested (e.g.,IsOpenmapping, time formatting, 24-hour vs 12-hour modes, text color updates).
✅ Looks Good
- Handler registration logic is correct:
#if ANDROIDblock registersTimePickerHandler2whenRuntimeFeature.IsMaterial3Enabled, else falls back toTimePickerHandler. Non-Android platforms unconditionally useTimePickerHandler. The removal of the unconditionalAddHandler<TimePicker, TimePickerHandler>()at line 99 (old) correctly avoids double-registration. - Dialog lifecycle management is solid:
RemoveListeners()is called beforeDismissAllowingStateLoss(), preventing theOnDismisscallback from firing whenHidePickerDialogis called explicitly. TheOnDismisspath (user dismisses via back/outside tap) correctly sets_dialog = nullbeforeUpdateIsOpenState. _isUpdatingIsOpenre-entrancy guard correctly prevents feedback loops betweenMapIsOpenandUpdateIsOpenState.Use24HourViewlogic matches the originalTimePickerHandlerexactly.MauiMaterialContextThemeWrapper.Create(context)usage in the primary constructor is consistent withMauiMaterialButton.WeakReference<TimePickerHandler2>in listener classes prevents the listeners from keeping the handler alive beyond its useful lifetime.DismissAllowingStateLoss()is used instead ofDismiss(), which is correct for scenarios where the fragment state may have already been saved (e.g., backgrounded activity).
6d1e724 to
3e62967
Compare
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Added UI tests and addressed all valid concerns. |
jfversluis
left a comment
There was a problem hiding this comment.
PR #33646 Code Review: [Android] Implement material3 support for TimePicker
Executive Summary
This PR implements Material3 TimePicker support for Android using conditional handler registration. While the implementation follows good patterns for lifecycle management and memory safety, there are critical bugs and design issues that must be addressed before merge.
Verdict: 🚫 CHANGES REQUESTED
Critical Issues (MUST FIX)
1. CRITICAL BUG: Operator Precedence Error in Use24HourView ⚠️
Location: TimePickerHandler2.Android.cs:237-238
// WRONG - Missing parentheses causes incorrect evaluation
bool Use24HourView => VirtualView is not null && (DateFormat.Is24HourFormat(PlatformView?.Context)
&& VirtualView.Format == "t" || VirtualView.Format == "HH:mm");Problem: Due to C# operator precedence (&& before ||), this evaluates as:
(DateFormat.Is24HourFormat(context) && Format == "t") || (Format == "HH:mm")
Bug scenario: If device is in 12-hour mode but user explicitly sets Format="HH:mm", the expression incorrectly returns true and shows a 24-hour dialog.
Fix:
bool Use24HourView => VirtualView is not null && DateFormat.Is24HourFormat(PlatformView?.Context)
&& (VirtualView.Format == "t" || VirtualView.Format == "HH:mm");Note: The existing TimePickerHandler.Android.cs has the SAME bug (lines 133-134). This should be fixed in BOTH handlers.
2. Code Duplication in TimePickerExtensions.cs
Location: TimePickerExtensions.cs - Lines 8-83
Problem: Four extension methods are duplicated verbatim for MauiTimePicker and MauiMaterialTimePicker:
UpdateFormat(lines 8-17 vs 14-17)UpdateTime(lines 19-28 vs 25-28)SetTime(lines 30-36 vs 38-44)UpdateTextColor(lines 46-63 vs 66-83)
Why it matters:
- 41 lines of duplicated code (49% of the file)
- Bug fixes must be applied twice (maintenance burden)
- Violates DRY principle
Recommended fix: Use generic extension methods or shared helper:
// Option 1: Generic constraint (preferred if both inherit from EditText)
public static void UpdateTextColor<T>(this T platformTimePicker, ITimePicker timePicker)
where T : EditText
{
// Single implementation
}
// Option 2: Shared helper method
internal static void UpdateTextColorImpl(EditText platformView, ITimePicker timePicker)
{
// Shared implementation
}
public static void UpdateTextColor(this MauiTimePicker picker, ITimePicker timePicker)
=> UpdateTextColorImpl(picker, timePicker);
internal static void UpdateTextColor(this MauiMaterialTimePicker picker, ITimePicker timePicker)
=> UpdateTextColorImpl(picker, timePicker);Major Issues (Should Fix)
3. Test Coverage: Limited Negative Testing
Location: Material3TimePickerFeatureTests.cs
Strengths:
✅ 21 comprehensive tests covering font, color, format, culture
✅ Good use of screenshot verification with tolerance
✅ Platform-guarded with #if ANDROID
✅ Proper test ordering
Gaps:
- ❌ No tests for picker dialog dismissal (cancel button, back button)
- ❌ No tests for IsOpen property toggle
- ❌ No tests for rapid open/close scenarios (race conditions)
- ❌ No tests for handler lifecycle (DisconnectHandler while dialog open)
- ❌ Limited keyboard/24-hour format tests
Recommendation: Add edge case tests:
[Test]
public void Material3TimePicker_CancelButton_DoesNotUpdateTime()
{
// Open picker, tap cancel, verify Time unchanged
}
[Test]
public void Material3TimePicker_RapidOpenClose_HandlesGracefully()
{
// Toggle IsOpen rapidly, verify no crashes
}
[Test]
public void Material3TimePicker_DisconnectWhileOpen_CleansUpProperly()
{
// Open picker, navigate away, verify no leaks
}4. Missing Tests for Use24HourView Logic
Given the operator precedence bug, add explicit tests:
[Test]
public void Material3TimePicker_Format_HHmm_12HourDevice_Shows24HourDialog()
{
// Set device to 12h mode, Format="HH:mm"
// Open picker, verify 24h clock shown
}
[Test]
public void Material3TimePicker_Format_t_12HourDevice_Shows12HourDialog()
{
// Set device to 12h mode, Format="t"
// Open picker, verify 12h clock shown
}Minor Issues (Good to Fix)
5. Documentation: TODO Comments Need Context
Location: Multiple files
// TODO: Material3: Make it public in .NET 11Recommendation: Add context about why these are internal:
// TODO: Material3: Make it public in .NET 11
// Currently internal because Material3 feature flag is experimental (defaults to false).
// Once Material3 is stable in .NET 11, promote to public API.6. Consistency: RemapForControls Not Updated
Location: AppHostBuilderExtensions.cs:251
The PR adds conditional handler registration but doesn't update TimePicker.RemapForControls(). Verify if TimePickerHandler2 needs a separate remap or if the existing one applies to both.
7. Null Safety: Minor Improvement Opportunity
Location: TimePickerHandler2.Android.cs:157
protected virtual MaterialTimePicker? CreateTimePickerDialog(int hour, int minute)
{
var dialog = new MaterialTimePicker.Builder()
.SetHour(hour)
.SetMinute(minute)
.SetTimeFormat(Use24HourView ? TimeFormat.Clock24h : TimeFormat.Clock12h)
.SetInputMode(MaterialTimePicker.InputModeClock)
.Build();
if (_positiveButtonClickListener is not null && _dismissListener is not null)
{
dialog?.AddOnPositiveButtonClickListener(_positiveButtonClickListener);
dialog?.AddOnDismissListener(_dismissListener);
}
return dialog;
}Issue: dialog can only be null if Build() throws, not returns null. The dialog?. checks are unnecessary.
Recommendation:
if (_positiveButtonClickListener is not null && _dismissListener is not null && dialog is not null)
{
dialog.AddOnPositiveButtonClickListener(_positiveButtonClickListener);
dialog.AddOnDismissListener(_dismissListener);
}Or better yet, assert non-null if Build() is guaranteed to return non-null:
var dialog = builder.Build() ?? throw new InvalidOperationException("MaterialTimePicker.Builder.Build() returned null");What's Good (Praise) ✅
1. Excellent Handler Lifecycle Management
protected override void DisconnectHandler(MauiMaterialTimePicker platformView)
{
if (_dialog is not null)
{
RemoveListeners();
if (_dialog.IsAdded) { _dialog.DismissAllowingStateLoss(); }
_dialog = null;
}
_positiveButtonClickListener?.Dispose();
_positiveButtonClickListener = null;
_dismissListener?.Dispose();
_dismissListener = null;
platformView.ShowPicker = null;
platformView.HidePicker = null;
base.DisconnectHandler(platformView);
}✅ Proper disposal of Java objects
✅ Null-checks before Dispose
✅ Dialog dismissal before cleanup
✅ Clear action delegates
2. Proper Use of WeakReference Pattern
internal class MaterialTimePickerPositiveButtonClickListener : Java.Lang.Object, View.IOnClickListener
{
readonly WeakReference<TimePickerHandler2> _handler;
public void OnClick(View? v)
{
if (!_handler.TryGetTarget(out var handler) || handler.VirtualView is null)
return;
// ...
}
}✅ Prevents memory leaks from Java→C# references
✅ Consistent with MAUI patterns
✅ Proper null-checks after TryGetTarget
3. Smart Dialog State Management
void ShowPickerDialog(TimeSpan? time)
{
// Prevent duplicate dialogs
if (_dialog is not null && (_dialog.IsVisible || _dialog.IsAdded))
return;
// FragmentActivity validation
if (Context?.GetActivity() is not FragmentActivity fragmentActivity ||
fragmentActivity.IsDestroyed || fragmentActivity.IsFinishing)
return;
// ...
}✅ Duplicate dialog prevention
✅ Activity lifecycle checks
✅ Defensive programming
4. First Production Use of Material3 Feature Flag
This PR pioneers the RuntimeFeature.IsMaterial3Enabled pattern in handler registration, setting precedent for future Material3 controls. The pattern is clean and well-structured.
Architectural Notes
Handler Registration Pattern (First Material3 Usage)
#if ANDROID
if (RuntimeFeature.IsMaterial3Enabled)
handlersCollection.AddHandler<TimePicker, TimePickerHandler2>();
else
handlersCollection.AddHandler<TimePicker, TimePickerHandler>();
#else
handlersCollection.AddHandler<TimePicker, TimePickerHandler>();
#endifAssessment: ✅ Correct approach. Keeps Material3 Android-only without affecting other platforms.
Future concern: As more Material3 handlers are added, consider extracting to a helper method to avoid repetitive #if blocks.
Testing Notes
CI Status: ✅ ALL PASSING
- Integration tests: ✅ Pass (Windows, macOS, iOS, Android)
- Build tests: ✅ Pass
- Screenshot tests: ✅ 19 snapshots committed for android-notch-36
Recommendation: Before merge, run UI tests with Material3 enabled to verify the handler is actually being used.
Summary of Required Changes
Must Fix (Blocking)
- ✅ Fix operator precedence in
Use24HourView(both handlers) - ✅ Eliminate code duplication in
TimePickerExtensions.cs
Should Fix (Strongly Recommended)
- ✅ Add negative testing (cancel, rapid toggle, disconnect while open)
- ✅ Add explicit tests for Use24HourView logic with different formats
Nice to Fix (Optional)
- Improve TODO comment context
- Verify RemapForControls applicability
- Simplify null-checks in CreateTimePickerDialog
Final Recommendation
Status: 🚫 CHANGES REQUESTED
The implementation demonstrates solid understanding of Android handler patterns, lifecycle management, and memory safety. However, the operator precedence bug is critical and will cause incorrect behavior in production. The code duplication significantly impacts maintainability.
Action items:
- Fix the Use24HourView operator precedence bug (CRITICAL)
- Refactor TimePickerExtensions to eliminate duplication (HIGH)
- Add edge case tests for dialog dismissal and lifecycle (MEDIUM)
- Add Use24HourView format tests (MEDIUM)
Once these are addressed, this will be a strong contribution to Material3 support.
For the Team
This is the first Material3 handler using the feature flag. Consider:
- Documenting this pattern in contributor guidelines
- Creating a Material3 handler template for future controls
- Planning Material3 migration strategy for other pickers (DatePicker, etc.)
Great work by @HarishwaranVijayakumar (Syncfusion) on this foundational Material3 implementation! 🎉
The core architecture is excellent - just needs these bug fixes and improvements before shipping.
AI Multi-Model Review — PR #33646Reviewed by: Claude Sonnet 4.5, Claude Opus 4.6, GPT 5.1 OverallSolid Material3 TimePicker implementation — good handler lifecycle, memory safety (WeakReference listeners), and consistent with existing Material3 patterns. 21 comprehensive screenshot tests. This sets a good precedent as the first However, there are a few issues that should be addressed before merge. 🔴 Operator Precedence Ambiguity in
|
|
@jfversluis, Addressed the valid concerns. |
<!-- 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. !!!!!!! --> ### Description of Change <!-- Enter description of the fix in this section --> This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of `TimePickerHandler2` and `MauiMaterialTimePicker` when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise. ### Material3 Time Picker Integration * Added new `TimePickerHandler2` class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (`src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs`) * Introduced `MauiMaterialTimePicker` control for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (`src/Core/src/Platform/Android/MauiMaterialTimePicker.cs`) ### Handler Registration Updates * Modified `AddControlsHandlers` method to register `TimePickerHandler2` for Android when Material3 is enabled, and fall back to the original handler otherwise. (`src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs`) [[1]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bR72-R83) [[2]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bL90) ### Platform Extension Methods * Added extension methods for updating format, time, and text color for `MauiMaterialTimePicker`, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (`src/Core/src/Platform/Android/TimePickerExtensions.cs`) Material Design Spec - [TimePicker](https://m3.material.io/components/time-pickers/specs) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #33645 ### Output | Material 2 |Material 3 | |----------|----------| | <img src="https://github.com/user-attachments/assets/13cf453c-93e4-4009-a465-9ed9975d9d18"> | <img src="https://github.com/user-attachments/assets/577cd1d1-79fe-4348-856e-8658ea969fd9"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- 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. !!!!!!! --> ### Description of Change <!-- Enter description of the fix in this section --> This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of `TimePickerHandler2` and `MauiMaterialTimePicker` when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise. ### Material3 Time Picker Integration * Added new `TimePickerHandler2` class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (`src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs`) * Introduced `MauiMaterialTimePicker` control for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (`src/Core/src/Platform/Android/MauiMaterialTimePicker.cs`) ### Handler Registration Updates * Modified `AddControlsHandlers` method to register `TimePickerHandler2` for Android when Material3 is enabled, and fall back to the original handler otherwise. (`src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs`) [[1]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bR72-R83) [[2]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bL90) ### Platform Extension Methods * Added extension methods for updating format, time, and text color for `MauiMaterialTimePicker`, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (`src/Core/src/Platform/Android/TimePickerExtensions.cs`) Material Design Spec - [TimePicker](https://m3.material.io/components/time-pickers/specs) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #33645 ### Output | Material 2 |Material 3 | |----------|----------| | <img src="https://github.com/user-attachments/assets/13cf453c-93e4-4009-a465-9ed9975d9d18"> | <img src="https://github.com/user-attachments/assets/577cd1d1-79fe-4348-856e-8658ea969fd9"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- 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. !!!!!!! --> ### Description of Change <!-- Enter description of the fix in this section --> This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of `TimePickerHandler2` and `MauiMaterialTimePicker` when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise. ### Material3 Time Picker Integration * Added new `TimePickerHandler2` class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (`src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs`) * Introduced `MauiMaterialTimePicker` control for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (`src/Core/src/Platform/Android/MauiMaterialTimePicker.cs`) ### Handler Registration Updates * Modified `AddControlsHandlers` method to register `TimePickerHandler2` for Android when Material3 is enabled, and fall back to the original handler otherwise. (`src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs`) [[1]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bR72-R83) [[2]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bL90) ### Platform Extension Methods * Added extension methods for updating format, time, and text color for `MauiMaterialTimePicker`, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (`src/Core/src/Platform/Android/TimePickerExtensions.cs`) Material Design Spec - [TimePicker](https://m3.material.io/components/time-pickers/specs) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #33645 ### Output | Material 2 |Material 3 | |----------|----------| | <img src="https://github.com/user-attachments/assets/13cf453c-93e4-4009-a465-9ed9975d9d18"> | <img src="https://github.com/user-attachments/assets/577cd1d1-79fe-4348-856e-8658ea969fd9"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- 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. !!!!!!! --> ### Description of Change <!-- Enter description of the fix in this section --> This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of `TimePickerHandler2` and `MauiMaterialTimePicker` when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise. ### Material3 Time Picker Integration * Added new `TimePickerHandler2` class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (`src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs`) * Introduced `MauiMaterialTimePicker` control for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (`src/Core/src/Platform/Android/MauiMaterialTimePicker.cs`) ### Handler Registration Updates * Modified `AddControlsHandlers` method to register `TimePickerHandler2` for Android when Material3 is enabled, and fall back to the original handler otherwise. (`src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs`) [[1]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bR72-R83) [[2]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bL90) ### Platform Extension Methods * Added extension methods for updating format, time, and text color for `MauiMaterialTimePicker`, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (`src/Core/src/Platform/Android/TimePickerExtensions.cs`) Material Design Spec - [TimePicker](https://m3.material.io/components/time-pickers/specs) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes dotnet#33645 ### Output | Material 2 |Material 3 | |----------|----------| | <img src="https://github.com/user-attachments/assets/13cf453c-93e4-4009-a465-9ed9975d9d18"> | <img src="https://github.com/user-attachments/assets/577cd1d1-79fe-4348-856e-8658ea969fd9"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- 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. !!!!!!! --> ### Description of Change <!-- Enter description of the fix in this section --> This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of `TimePickerHandler2` and `MauiMaterialTimePicker` when Material3 is enabled, while maintaining compatibility with the existing time picker otherwise. ### Material3 Time Picker Integration * Added new `TimePickerHandler2` class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (`src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs`) * Introduced `MauiMaterialTimePicker` control for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (`src/Core/src/Platform/Android/MauiMaterialTimePicker.cs`) ### Handler Registration Updates * Modified `AddControlsHandlers` method to register `TimePickerHandler2` for Android when Material3 is enabled, and fall back to the original handler otherwise. (`src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs`) [[1]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bR72-R83) [[2]](diffhunk://#diff-66e353858f3298f672f1eb103758a50e379019a1d8efe1b845de3779e7323b8bL90) ### Platform Extension Methods * Added extension methods for updating format, time, and text color for `MauiMaterialTimePicker`, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (`src/Core/src/Platform/Android/TimePickerExtensions.cs`) Material Design Spec - [TimePicker](https://m3.material.io/components/time-pickers/specs) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #33645 ### Output | Material 2 |Material 3 | |----------|----------| | <img src="https://github.com/user-attachments/assets/13cf453c-93e4-4009-a465-9ed9975d9d18"> | <img src="https://github.com/user-attachments/assets/577cd1d1-79fe-4348-856e-8658ea969fd9"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
## 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!
Description of Change
This pull request introduces a new Material3-based time picker for Android and updates the handler registration and platform-specific extensions to support it. The main goal is to enable the use of
TimePickerHandler2andMauiMaterialTimePickerwhen Material3 is enabled, while maintaining compatibility with the existing time picker otherwise.Material3 Time Picker Integration
TimePickerHandler2class for Android, implementing a Material3-style time picker dialog, with custom mapping and event handling for properties such as time, format, background, and open/close state. (src/Core/src/Handlers/TimePicker/TimePickerHandler2.Android.cs)MauiMaterialTimePickercontrol for Android, which wraps the Material3 time picker UI and exposes methods for showing/hiding the picker and updating its appearance. (src/Core/src/Platform/Android/MauiMaterialTimePicker.cs)Handler Registration Updates
AddControlsHandlersmethod to registerTimePickerHandler2for Android when Material3 is enabled, and fall back to the original handler otherwise. (src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs) [1] [2]Platform Extension Methods
MauiMaterialTimePicker, mirroring existing methods for the legacy time picker, and ensuring correct formatting and appearance. (src/Core/src/Platform/Android/TimePickerExtensions.cs)Material Design Spec - TimePicker
Issues Fixed
Fixes #33645
Output