[material_ui] Add global Material style variant#11931
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new StyleVariant enum to define Material Design style variants, specifically material3 and material3Expressive. It integrates this variant into ThemeData, updating its constructors, copyWith, lerp, equality checks, and diagnostics. Corresponding unit tests have been added to verify the behavior of the new property. There are no review comments, and no additional feedback is provided.
| InteractiveInkFeatureFactory? splashFactory, | ||
| bool? useMaterial3, | ||
| bool? useSystemColors, | ||
| StyleVariant? variant, |
There was a problem hiding this comment.
I'm also open to other name suggestions! I just stole Tong's proposal for the name :)
There was a problem hiding this comment.
No better ideas from me, looks good.
|
The PR that adds the new variant should also add assertions to all widgets and the global theme at the same time. Otherwise developers would be misled and use the unsupported style by mistake. |
|
I added asserts to both the component theme data and the component build methods.
By default, each component's MD style will fall back to Question:If one component doesn't have m3e update now but later it does, do we add the assertion and will it become a breaking change? For example, if previously a component doesn't have m3e update, this will pass: ThemeData(variant: .material3expressive, xxxWidgetTheme: xxxWidgetThemeData());But if later it has m3e update and if we add the assertion, this will fail because m3e is not supported. In this case, do we just ignore adding an assertion or use a |
166afcd to
539218b
Compare
justinmc
left a comment
There was a problem hiding this comment.
Quick review here. I need to finish reading you and @dkwingsmt's latest doc, but currently my main question is about our deprecation strategy. To me it looks like the simplest thing would be: don't deprecate anything until it's possible to migrate completely off of M3, and at that point deprecate the enum value StyleVariant.material3 and change the default value to material3Expressive in a major version bump. Is that what you had in mind? Or just tell me to go read the doc if it's all in there :)
Also, it would be insightful to see a draft PR built on top of this PR that adds M3E support to one of the easier widgets here. No need to spend a bunch of time putting that together, but if you have already tried it or thought it through, I'd be interested to see what it looks like exactly.
| InteractiveInkFeatureFactory? splashFactory, | ||
| bool? useMaterial3, | ||
| bool? useSystemColors, | ||
| StyleVariant? variant, |
There was a problem hiding this comment.
No better ideas from me, looks good.
| cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault(); | ||
| extensions ??= <ThemeExtension<dynamic>>[]; | ||
| adaptations ??= <Adaptation<Object>>[]; | ||
| variant ??= StyleVariant.material3; |
There was a problem hiding this comment.
How does this interact with other things that indicate material version, like useMaterial3, or Typography.material2021?
| InteractiveInkFeatureFactory? splashFactory, | ||
| bool? useMaterial3, | ||
| bool? useSystemColors, | ||
| StyleVariant? variant, |
There was a problem hiding this comment.
Is this really ever nullable, or this is just to set the default?
| final StyleVariant effectiveVariant = appBarTheme.variant ?? theme.variant; | ||
| assert(effectiveVariant != .material3Expressive, kUnsupportedStyleVariantAssertionMessage); |
There was a problem hiding this comment.
So is the StyleVariant always taken from the theme and never passed into a widget as a parameter?
| final ThemeData theme = Theme.of(context); | ||
| final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context); | ||
| final AppBarThemeData appBarTheme = AppBarTheme.of(context); | ||
| final StyleVariant effectiveVariant = appBarTheme.variant ?? theme.variant; |
There was a problem hiding this comment.
This is a naive question because I'm forgetting the details of Material's theming, but would it be possible for the AppBarTheme to receive its default value of variant from Theme, so that you don't have to do this xTheme.variant ?? theme.variant everywhere? It strikes me as easy to forget.
If that's not possible or not how this kind of thing is typically done in theming, then never mind.
| titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), | ||
| systemOverlayStyle: t < 0.5 ? a.systemOverlayStyle : b.systemOverlayStyle, | ||
| actionsPadding: EdgeInsetsGeometry.lerp(a.actionsPadding, b.actionsPadding, t), | ||
| variant: t < 0.5 ? a.variant : b.variant, |
There was a problem hiding this comment.
Nit: Would it be useful to abstract this into a StyleVariant.lerp? I see this is done in a handful of places.
This change definitely need add changelog and bump minor version when it land into main. but it depends on the release strategy material_ui use at that moment, we can revisit this when we want to merge the feature branch back to main |
Yes. Most of your understanding is correct! But just wanted to be more clear, since we now consider M3e as an expansion pack for M3, we allow M3 and M3e (and potential M3xxx) to exist at the same time. We don't deprecate M3 until the MD website has clearly mentioned M3 is outdated/M4 is introduced. Until then, we don't need to worry about the deprecation. As for the default value changing, still because we allow M3, M3e and potential expansion packs exist at the same time, we should treat M3e as an opt-in flag, so that we don't need to explicitly change the default value; we will keep m3 as the default value. If later we have M4, we introduce a new library because it would be considered as a "major change". When M4 is fully supported, we should start to deprecate and remove M3 + M3E + M3X folder all at once. And because M3/M3e and M4 are in 2 folders and no more tangling will happen, removing M3 and its variants should be easy! As the heavy tangling between M2 and M3, we should gradually deprecate M2 and
Sure! I'll make |
8bfc3f1 to
3084bc4
Compare
There was a problem hiding this comment.
adding the global variant on theme data LGTM, but I am not too sure about start adding the widget specific one without actual implementation. Shouldn't we add individual widget theme variant when we actually implement the m3e design for the widget? it doesn't add value as of now because developer can't set anything meaningful besides lock us in on the API for those widgets
| 'The color and backgroundColor parameters mean the same thing. Only specify one.', | ||
| ); | ||
| ), | ||
| assert(variant != .material3Expressive, kUnsupportedStyleVariantAssertionMessage); |
There was a problem hiding this comment.
I think we should refactor this into a share debug method in debug.dart. if we add more enum variant or update the string, some of these assert may produce broken messaging if we forgot to update them
|
|
||
| if (_addPadding) { | ||
| final bool useMaterial3 = Theme.of(context).useMaterial3; | ||
| final bool useMaterial3 = theme.useMaterial3; |
There was a problem hiding this comment.
can the useMaterial3 == false at this point? feels weird we have one flag for flipping material 3 and material 2, and another flag to flip between material3 and m3e. is there something we can do to unify them?
This PR is to add a global
StyleVariantsetting toThemeDataso Material components can choose between Material 3 and Material 3 Expressive defaults fromThemeData.ThemeData.variantcurrently defaults toStyleVariant.material3, preserving existing behavior unless apps explicitly opt in toStyleVariant.material3Expressive.Why default to Material 3?
To align with Cupertino's proposal, which does not provide a default value for
StyleVariant, I also considered makingThemeData.variantnullable and adding a runtime check similar todebugCheckHasMaterial. Migrated components could then require an explicit style variant ancestor, and users could receive warnings when a style variant is deprecated.However, that approach seems too strict for the start of the migration:
ThemeorMaterialApp.ThemeData.variantby using something likedebugCheckHasStyleVariant, requiring an explicit variant would make those cases fail even though the default behavior should still be Material 3. That breaking change would mainly serve the deprecation flow rather than improve user behavior today.Therefore, this PR gives the global variant a default value of
StyleVariant.material3. This keeps the migration non-breaking while still giving components a single global source of truth for opting into Material 3 Expressive. If the default ever changes later, that can happen as a separate, intentional migration step. In other words, this defers the breaking change until the point where we intentionally change the defaultvariant.API scope
This PR only adds the
variantapi toThemeData. It does not add aMaterialApp.variantshortcut because apps can already configure the value with:Also wanted to mention,
StyleVariantis intended to select between Material 3 variants. Components that still supportuseMaterial3: falseshould continue to use their Material 2 defaults whenuseMaterial3is false, regardless ofThemeData.variant. But we should gradually deprecateuseMaterial3since M2 is too out of date.Open questions
Should this PR update CHANGELOG.md and bump pubspec.yaml?
This is a new public API. However, since this branch targets
m3e_migrationrather than main, I left those files unchanged for now and would like reviewer guidance on whether migration-branch PRs should update package release metadata immediately or defer that until changes are moved to main.Pre-Review Checklist
[shared_preferences]///).