Skip to content

Conversation

@NirmalKumarYuvaraj
Copy link
Contributor

@NirmalKumarYuvaraj NirmalKumarYuvaraj commented Dec 12, 2025

Description of Change

This pull request adds support for Material 3 styling for the Switch control on Android in .NET MAUI. It introduces a new Material 3 switch handler, configures runtime feature toggling for Material 3, and adds the necessary Android resources and theming infrastructure for Material 3 support.

Material 3 Switch support and theming:

  • Introduced a new MaterialSwitchHandler for Android, which uses the Material 3 MauiMaterialSwitch when the Material 3 feature is enabled. This handler includes property mapping for IsOn, TrackColor, and ThumbColor. (src/Core/src/Handlers/Switch/MaterialSwitchHandler.Android.cs)
  • Added MauiMaterialSwitch class, which wraps the Material 3 switch widget and applies the correct Material 3 theme context. (src/Core/src/Platform/Android/Material3Controls/MauiMaterialSwitch.cs)
  • Updated SwitchExtensions to support updating properties (IsOn, TrackColor, ThumbColor) on both traditional and Material 3 switches. (src/Core/src/Platform/Android/SwitchExtensions.cs) [1] [2]

Feature toggle and runtime configuration:

  • Added the IsMaterial3Enabled runtime feature flag, with a default of false, and logic to read it from app context or MSBuild properties. (src/Core/src/RuntimeFeature.cs, src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets) [1] [2] [3]
  • Modified MauiMaterialContextThemeWrapper to select the correct base theme depending on the Material 3 feature flag. (src/Core/src/Platform/Android/MauiMaterialContextThemeWrapper.cs)

Resource and style additions:

  • Added Material 3 color definitions and styles for Android, including light and dark theme palettes and base styles for Material 3. (src/Core/src/Platform/Android/Resources/values/colors-material3.xml, src/Core/src/Platform/Android/Resources/values/styles-material3.xml) [1] [2]

Handler registration logic:

  • Updated handler registration to use MaterialSwitchHandler for Switch controls on Android when Material 3 is enabled, otherwise falling back to the default handler. (src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs) [1] [2]

These changes collectively enable opt-in Material 3 switch styling on Android, controlled by a feature flag, and lay the groundwork for broader Material 3 support in .NET MAUI.

Previous base style PR: #33074

Issues Fixed

Fixes #33131

Screenshots

Material Design Spec - Switch

Material 2  Material 3 
   

@dotnet-policy-service dotnet-policy-service bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Dec 12, 2025
? isEnabled
: EnableAspireByDefault;

#if NET10_0_OR_GREATER
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary? I thought that main is always .NET 10 or higher right now.

Copy link

Choose a reason for hiding this comment

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

maybe because Maui 9 officially expires on May 12, 2026, if necessary they can work on it

@sheiksyedm sheiksyedm marked this pull request as ready for review December 12, 2025 10:22
Copilot AI review requested due to automatic review settings December 12, 2025 10:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements Material 3 support for the Switch control on Android in .NET MAUI. The implementation is controlled by a runtime feature flag (IsMaterial3Enabled) that defaults to false, allowing developers to opt-in via the UseMaterial3 MSBuild property. When enabled, Switch controls use the new Material 3 styling with the Material Design 3 components library.

Key Changes

  • Added runtime feature flag IsMaterial3Enabled with MSBuild property binding for opt-in Material 3 support
  • Implemented MaterialSwitchHandler and MauiMaterialSwitch to provide Material 3 switch functionality on Android
  • Extended SwitchExtensions with Material 3-specific property update methods for theme-aware color management
  • Added comprehensive Material 3 color tokens and style definitions for Android resources

Reviewed changes

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

Show a summary per file
File Description
src/Core/src/RuntimeFeature.cs Adds IsMaterial3Enabled runtime feature flag with conditional compilation for .NET 10+
src/Core/src/Platform/Android/SwitchExtensions.cs Extends switch utilities with Material 3 switch support for IsOn, TrackColor, and ThumbColor properties
src/Core/src/Platform/Android/Resources/values/styles-material3.xml Defines Material 3 base themes, splash themes, and action mode styles for Android
src/Core/src/Platform/Android/Resources/values/colors-material3.xml Adds complete Material 3 color palette with light/dark theme tokens and state overlay colors
src/Core/src/Platform/Android/MauiMaterialContextThemeWrapper.cs Updates theme wrapper to select Material 3 or Material 2 base theme based on feature flag
src/Core/src/Platform/Android/Material3Controls/MauiMaterialSwitch.cs New internal wrapper class for Material 3 switch widget with proper theme context
src/Core/src/Handlers/Switch/MaterialSwitchHandler.Android.cs New handler for Material 3 switch with property mapping and change listener implementation
src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs Adds conditional handler registration logic to use MaterialSwitchHandler when Material 3 is enabled on Android
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets Configures MSBuild property UseMaterial3 to set the Material 3 runtime feature flag

Comment on lines 60 to 64
else if (thumbColor is null && materialSwitch is MauiMaterialSwitch mauiSwitch)
{
materialSwitch.ThumbTintList = _defaultThumbTintList;

}
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The condition thumbColor is null is redundant and makes the logic confusing. The outer condition on line 56 already checks if thumbColor is not null, so the else-if on line 60 will only execute when thumbColor is null. The additional thumbColor is null check in the condition is therefore unnecessary.

Additionally, the materialSwitch is MauiMaterialSwitch type check appears unnecessary since this is an internal extension method specifically for Material Switch. If there's a specific reason this check is needed, it should be documented.

Copilot uses AI. Check for mistakes.
else if (thumbColor is null && materialSwitch is MauiMaterialSwitch mauiSwitch)
{
materialSwitch.ThumbTintList = _defaultThumbTintList;

Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

There is an extra blank line inside the if block that should be removed for consistency with the codebase style.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
static ColorStateList? _defaultTrackTintList;
static ColorStateList? _defaultThumbTintList;
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The static fields _defaultTrackTintList and _defaultThumbTintList are shared across all switch instances (both traditional and Material3). This could cause issues when both switch types are used in the same application. The first switch instance initialized will set these defaults, and those defaults will be incorrectly applied to the other switch type when colors are reset to null.

Consider maintaining separate default tint lists for each switch type, or storing defaults per-instance using a different mechanism such as attached properties or a dictionary keyed by switch instance.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +105
internal partial class MaterialSwitchHandler : ViewHandler<ISwitch, MauiMaterialSwitch>
{
public static PropertyMapper<ISwitch, MaterialSwitchHandler> Mapper =
new(ViewMapper)
{
[nameof(ISwitch.IsOn)] = MapIsOn,
[nameof(ISwitch.TrackColor)] = MapTrackColor,
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
};

public static CommandMapper<ISwitch, MaterialSwitchHandler> CommandMapper =
new(ViewCommandMapper);

MaterialSwitchCheckedChangeListener? _changeListener;

public MaterialSwitchHandler() : base(Mapper, CommandMapper)
{
}

protected override MauiMaterialSwitch CreatePlatformView()
{
return new MauiMaterialSwitch(Context);
}

protected override void ConnectHandler(MauiMaterialSwitch platformView)
{
_changeListener = new MaterialSwitchCheckedChangeListener(this);
platformView.SetOnCheckedChangeListener(_changeListener);

base.ConnectHandler(platformView);
}

protected override void DisconnectHandler(MauiMaterialSwitch platformView)
{
platformView.SetOnCheckedChangeListener(null);
_changeListener?.Dispose();
_changeListener = null;

base.DisconnectHandler(platformView);
}

public override Size GetDesiredSize(double widthConstraint, double heightConstraint)
{
Size size = base.GetDesiredSize(widthConstraint, heightConstraint);

if (size.Width == 0)
{
int width = (int)widthConstraint;

if (widthConstraint <= 0)
width = (int)(Context?.GetThemeAttributeDp(global::Android.Resource.Attribute.SwitchMinWidth) ?? 0);

size = new Size(width, size.Height);
}

return size;
}

public static void MapIsOn(MaterialSwitchHandler handler, ISwitch view)
{
handler.PlatformView?.UpdateIsOn(view);
}

public static void MapTrackColor(MaterialSwitchHandler handler, ISwitch view)
{
handler.PlatformView?.UpdateTrackColor(view);
}

public static void MapThumbColor(MaterialSwitchHandler handler, ISwitch view)
{
handler.PlatformView?.UpdateThumbColor(view);
}

void OnCheckedChanged(bool isOn)
{
if (VirtualView is null || VirtualView.IsOn == isOn)
return;

VirtualView.IsOn = isOn;
}

sealed class MaterialSwitchCheckedChangeListener : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
readonly WeakReference<MaterialSwitchHandler> _handler;

public MaterialSwitchCheckedChangeListener(MaterialSwitchHandler handler)
{
_handler = new WeakReference<MaterialSwitchHandler>(handler);
}

void CompoundButton.IOnCheckedChangeListener.OnCheckedChanged(CompoundButton? buttonView, bool isToggled)
{
if (_handler.TryGetTarget(out var handler))
{
handler.OnCheckedChanged(isToggled);
}
}
}
} No newline at end of file
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The new MaterialSwitchHandler lacks test coverage. Given that the repository has comprehensive tests for SwitchHandler (as seen in src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs), the new MaterialSwitchHandler should have equivalent test coverage to ensure Material3 switch behavior works correctly, especially for property mapping (IsOn, TrackColor, ThumbColor) and event handling.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +26
internal class MauiMaterialSwitch : MaterialSwitch
{
public MauiMaterialSwitch(Context context)
: base(MauiMaterialContextThemeWrapper.Create(context))
{
}

protected MauiMaterialSwitch(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}

public MauiMaterialSwitch(Context context, IAttributeSet? attrs) : base(MauiMaterialContextThemeWrapper.Create(context), attrs)
{
}

public MauiMaterialSwitch(Context context, IAttributeSet? attrs, int defStyleAttr) : base(MauiMaterialContextThemeWrapper.Create(context), attrs, defStyleAttr)
{
}
}
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The MauiMaterialSwitch class lacks XML documentation comments. Since this is a new internal class that wraps the Material 3 switch widget, it would benefit from documentation explaining its purpose and why it exists (i.e., to apply the correct Material 3 theme context to the Material Switch).

Copilot uses AI. Check for mistakes.
@rmarinho
Copy link
Member

rmarinho commented Dec 12, 2025

PR #33132 Code Review Summary

Title: [Android] Implemented Material3 Support for Switch Control

Overall Assessment

✅ Well-structured implementation following MAUI patterns, but has critical bugs and unrelated changes that need addressing.

Critical Issues

  1. 🔴 Static field collision bug - _defaultTrackTintList and _defaultThumbTintList in SwitchExtensions are shared globally. This will cause incorrect theming when an app uses both Material 2 and Material 3 switches.

  2. Redundant logic - UpdateThumbColor has unnecessary checks (already validated by outer if)

Medium Issues

  1. Missing test coverage - No UI tests for MaterialSwitchHandler
  2. Unclear #if NET10_0_OR_GREATER directive - May be unnecessary on main branch
  3. Unrelated eng/ infrastructure changes - Build system files from merge
  4. Deleted README.md - Unrelated deletion

Minor Issues

  1. Missing XML documentation on MauiMaterialSwitch
  2. Extra blank line

Recommendations

Must fix:

  • Fix static field collision (separate defaults per switch type)
  • Remove redundant checks in UpdateThumbColor
  • Remove unrelated changes (eng/, README.md)

Should add:

  • UI tests for Material 3 switch
  • Tests for mixed Material 2/3 usage
  • XML documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-switch Switch community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Material3 Support for Switch Control

5 participants