Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add brightnessOf method for theme #163733

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rkishan516
Copy link
Contributor

Feat: Add brightnessOf method for theme
fixes: #163393

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Feb 20, 2025
@rkishan516
Copy link
Contributor Author

This PR doesn't include implementation of InheritedModel for Theme.

return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context);
}

/// Retrieves the [Brightness] to use for descendant Cupertino widgets, based
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

Suggested change
/// Retrieves the [Brightness] to use for descendant Cupertino widgets, based
/// Retrieves the [Brightness] to use for descendant Material widgets, based
/// on the value of [ThemeData.brightness] in the given [context].
///
/// If no [InheritedTheme] can be found in the given [context], it will fall
/// back to [MediaQueryData.platformBrightness].
///
/// Returns null if no valid [InheritedTheme] or [MediaQuery] widgets exist in
/// the ancestry tree.
///
/// See also:
///
/// * [ThemeData.brightness], the property that takes precedence over
/// [MediaQueryData.platformBrightness] for descendant Material widgets.
/// * [brightnessOf], which throws if no valid [InheritedTheme] or
/// [MediaQuery] exists, instead of returning null.

@@ -44,7 +44,7 @@ void main() {
await tester.tap(find.byKey(popupMenuButtonKey));
await tester.pump(const Duration(seconds: 1));

expect(Theme.of(tester.element(find.text('menuItem'))).brightness, equals(Brightness.dark));
expect(Theme.brightnessOf(tester.element(find.text('menuItem'))), equals(Brightness.dark));
Copy link
Contributor

Choose a reason for hiding this comment

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

I would propose to not do this change in this PR, we can probably do all the ThemeData.brightness changes in a follow up PR, after this one lands. (which probably makes it a bit easier to review)

Instead, I would test the behavior of brightnessOf / maybeBrightnessOf in separate tests:

  1. brightnessOf returns the correct brightness if only a MediaQuery is provided
  2. brightnessOf returns the correct brightness if both a MediaQuery & ThemeData are provided
  3. brightnessOf throws if neither a MediaQuery or ThemeData is provided
  4. maybeBrightnessOf returns the correct brightness if only a MediaQuery is provided
  5. maybeBrightnessOf returns the correct brightness if both a MediaQuery & ThemeData are provided
  6. maybeBrightnessOf returns null if neither a MediaQuery or ThemeData is provided

I will file a follow up issue to do the former sparately.

Copy link
Contributor Author

@rkishan516 rkishan516 Feb 21, 2025

Choose a reason for hiding this comment

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

Sure, let me create another test which just verify that Theme.brightnessOf works in all scenario

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@navaronbracke I don't think scenario 3 and 6 can be produced, because in testing media query always return platform brightness.

Copy link
Contributor

@navaronbracke navaronbracke Feb 21, 2025

Choose a reason for hiding this comment

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

For these tests I would just pump a widget with the WidgetTester:

  1. Only pumps a MediaQuery widget with a Builder as child (to get the context for the brightness test)
  2. Pumps both a MediaQuery & a Theme widget, with a Builder as child
  3. Pumps only a Builder widget and never pumps a MediaQuery or Theme widget

Cases 4-6 are variations, but use the other method.

You should be able to give the MediaQuery a custom MediaQueryData, to force the brightness?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, MediaQuery takes data as input.

await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(platformBrightness: Brightness.dark),
child: Container(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I would use a SizedBox, instead of a Container to make the tests a little lighter.

await tester.pumpWidget(
Theme(
data: ThemeData(brightness: Brightness.light),
child: MediaQuery(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the MediaQuery be above the Theme, for consistency with the real widget tree in MaterialApp?

///
/// See also:
///
/// * [ThemeData.brightness], the property takes precedence over
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// * [ThemeData.brightness], the property takes precedence over
/// * [ThemeData.brightness], the property that takes precedence over

///
/// * [maybeBrightnessOf], which returns null if no valid [InheritedTheme] or
/// [MediaQuery] exists, instead of throwing.
/// * [ThemeData.brightness], the property takes precedence over
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// * [ThemeData.brightness], the property takes precedence over
/// * [ThemeData.brightness], the property that takes precedence over

/// * [maybeBrightnessOf], which returns null if no valid [InheritedTheme] or
/// [MediaQuery] exists, instead of throwing.
/// * [ThemeData.brightness], the property takes precedence over
/// [MediaQueryData.platformBrightness] for descendant widgets.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the mention of Material, since Theme is a Material thing and not under the widgets library.

Suggested change
/// [MediaQueryData.platformBrightness] for descendant widgets.
/// [MediaQueryData.platformBrightness] for descendant Material widgets.

@@ -161,6 +161,48 @@ class Theme extends StatelessWidget {
.resolveFrom(context);
}

/// Retrieves the [Brightness] to use for descendant widgets, based
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the mention of Material, since Theme is a Material thing and not under the widgets library.

Suggested change
/// Retrieves the [Brightness] to use for descendant widgets, based
/// Retrieves the [Brightness] to use for descendant Material widgets, based

return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context);
}

/// Retrieves the [Brightness] to use for descendant widgets, based
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the mention of Material, since Theme is a Material thing and not under the widgets library.

Suggested change
/// Retrieves the [Brightness] to use for descendant widgets, based
/// Retrieves the [Brightness] to use for descendant Material widgets, based

/// See also:
///
/// * [ThemeData.brightness], the property takes precedence over
/// [MediaQueryData.platformBrightness] for descendant widgets.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep the mention of Material, since Theme is a Material thing and not under the widgets library.

Suggested change
/// [MediaQueryData.platformBrightness] for descendant widgets.
/// [MediaQueryData.platformBrightness] for descendant Material widgets.

Copy link
Contributor

@navaronbracke navaronbracke left a comment

Choose a reason for hiding this comment

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

Minor docs correction and a suggestion for some missing tests.

/// If no [InheritedTheme] can be found in the given [context], or its `brightness`
/// is null, it will fall back to [MediaQueryData.platformBrightness].
///
/// Throws an exception if no valid [InheritedTheme] or [MediaQuery] widgets
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Throws an exception if no valid [InheritedTheme] or [MediaQuery] widgets

I had a look at the implementation of https://api.flutter.dev/flutter/widgets/MediaQuery/platformBrightnessOf.html

and it returns Brightness.light instead of throwing if there is no MediaQuery.
So you can remove the phrase that says it throws.

@rkishan516 rkishan516 force-pushed the brightness-of-theme branch 3 times, most recently from 3cfa06b to b6c93e8 Compare February 22, 2025 11:54
Copy link
Contributor

@navaronbracke navaronbracke left a comment

Choose a reason for hiding this comment

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

Some minor docs adjustments, to match the actual behavior of the method that is used, along with a clarification for one test.

/// See also:
///
/// * [maybeBrightnessOf], which returns null if no valid [InheritedTheme] or
/// [MediaQuery] exists, instead of throwing.
Copy link
Contributor

Choose a reason for hiding this comment

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

Since platformBrightnessOf does not throw, but returns a default, I think we can tweak the wording here.

Suggested change
/// [MediaQuery] exists, instead of throwing.
/// [MediaQuery] exists.

/// If no [InheritedTheme] or [MediaQuery] can be found in the given [context], it will
/// return null.
///
/// Returns null if no valid [InheritedTheme] or [MediaQuery] widgets exist in
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this phrase? Currently it says returns null twice

///
/// * [ThemeData.brightness], the property that takes precedence over
/// [MediaQueryData.platformBrightness] for descendant Material widgets.
/// * [brightnessOf], which throws if no valid [InheritedTheme] or
Copy link
Contributor

Choose a reason for hiding this comment

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

Same tweak here, since that method does not throw.

Suggested change
/// * [brightnessOf], which throws if no valid [InheritedTheme] or
/// * [brightnessOf], which returns a default value if no valid [InheritedTheme] or

testWidgets('returns Brightness.light when no theme or media query is present', (
WidgetTester tester,
) async {
await tester.pumpWidget(const SizedBox());
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to prevent the View from adding the MediaQuery here, otherwise there is an implicit MediaQuery

Suggested change
await tester.pumpWidget(const SizedBox());
// Prevent the implicitly added View from adding a MediaQuery
await tester.pumpWidget(
RawView(view: FakeFlutterView(tester.view, viewId: 77), child: const SizedBox()),
wrapWithView: false,
);

@navaronbracke
Copy link
Contributor

@rkishan516 While to me this PR looks good now, I will have to defer to people on the framework team for a review / explicit approval. So you will have to be patient a little :)

See https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#when for more info on the review process.

@rkishan516
Copy link
Contributor Author

@rkishan516 While to me this PR looks good now, I will have to defer to people on the framework team for a review / explicit approval. So you will have to be patient a little :)

See https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#when for more info on the review process.

Sure and Thanks for helping @navaronbracke.

@rkishan516 rkishan516 force-pushed the brightness-of-theme branch 2 times, most recently from 95dd5a5 to 227f414 Compare March 1, 2025 01:20
@dkwingsmt dkwingsmt requested review from dkwingsmt and QuncCccccc and removed request for navaronbracke March 12, 2025 18:50
Copy link
Contributor

@QuncCccccc QuncCccccc left a comment

Choose a reason for hiding this comment

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

Can we use Theme.of(context).brightness to detect the brightness?

@navaronbracke
Copy link
Contributor

That is how you would do this today. However, this PR aims to make it explicit, using a named method, and is a step towards eventually making Theme an InheritedModel. The latter is the main reasoning behind this change.

@dkwingsmt
Copy link
Contributor

@navaronbracke I'm not familiar with this work, but is there an issue tracking our progress (and discussions, if any) towards making Theme an InheritedModel?

@navaronbracke
Copy link
Contributor

@nate-thegrate made a design doc that talks about this a little in https://docs.google.com/document/d/1MfU6gkgIgXCxa5E25sPYuAP5Bmgehyl3Hk7yDH24zl0/edit?tab=t.0

@dkwingsmt
Copy link
Contributor

dkwingsmt commented Mar 14, 2025

I've asked @MitchellGoodwin and it seems the conversation didn't reached a point were a decision was made. Although I agree with the motivation of the work, I think it'd be better not to move forward until we have agreed on the exact design and plan.

If you would like to drive this project, you can start by writing a more detailed design doc with thorough planning and introduction, and we can seek comments or even host a Dash forum for discussion.

@MitchellGoodwin
Copy link
Contributor

Outside of InheritedModel, there is value in adding a brightnessOf method to Material for the sake of API parity since Cupertino has a brightnessOf method. Material and Cupertino handle their brightness differently though, in such a way were I don't think this method would decrease rebuilds the way it does for Cupertino theme.

@navaronbracke
Copy link
Contributor

Outside of InheritedModel, there is value in adding a brightnessOf method to Material for the sake of API parity since Cupertino has a brightnessOf method. Material and Cupertino handle their brightness differently though, in such a way were I don't think this method would decrease rebuilds the way it does for Cupertino theme.

Yes, feature parity between the two was also something I mentioned in the OP of #163393

@rkishan516
Copy link
Contributor Author

Outside of InheritedModel, there is value in adding a brightnessOf method to Material for the sake of API parity since Cupertino has a brightnessOf method. Material and Cupertino handle their brightness differently though, in such a way were I don't think this method would decrease rebuilds the way it does for Cupertino theme.

If we are able to push Theme as InheritedModel, then later part will not be an issue. If we decide to go with migrating Theme to InheritedModel, I won't mind doing that change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Theme.brightnessOf(context) / Theme.maybeBrightnessOf(context)
5 participants