Skip to content

Fix Radio's painters - #170321

Merged
auto-submit[bot] merged 4 commits into
flutter:masterfrom
ValentinVignal:flutter/fix-radio-painter
Jun 25, 2025
Merged

auto-submit[bot] merged 4 commits into
flutter:masterfrom
ValentinVignal:flutter/fix-radio-painter

Conversation

@ValentinVignal

Copy link
Copy Markdown
Contributor

While working on #168787 as a follow-up of #169415, I noticed that the tests introduced in #169415 were incorrect. However, because of the way the Radio implements paint and reuses 1 painter, it was not possible to properly assert the paints in the test.

This PR fixes it

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions Bot added framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages labels Jun 10, 2025
@ValentinVignal
ValentinVignal force-pushed the flutter/fix-radio-painter branch from a9a35c8 to 5d0fd66 Compare June 10, 2025 05:56

@chunhtai chunhtai left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, Looking at the code recreating paint everytime should be light weight enough, so this should be fine. Just some question about the test

Material.of(tester.element(find.byType(Radio<int>))),
paints
..rect()
..circle(color: Colors.transparent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't get why this won't record correctly if reuse paint, looking a the code, it just call renderObject.paint on the TestRecordingPaintingContext which will record all the push layer. Can you explain a bit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure!

The previous implementation was reusing the same Paint and was something similar to:

// Draw background.
final Paint paint = Paint()..color = Colors.transparent;
canvas.drawCircle(backgroundOffset, backgroundRadius, paint);

// Draw border.
paint..color = Colors.red; // Red for simplicity here
canvas.drawCircle(borderOffset, borderRadius, paint);

I wanted to verify the background and the border were painted, for that, I was using mock canvas with:

expect(
  radioFinder,
  paints
    ..circle(color: Colors.transparent)
    ..circle(color: Colors.red),
);

But it was failing, it couldn't "find" the ..circle(color: Colors.transparent) part and the reason was because the reuse of the reference of Paint paint.

TestRecordingPaintingContext records the invocations and here is what was happening during a test:

/*
There are no invocations recorded yet:
invocations: []
*/

// Draw background.
final Paint paint = Paint()..color = Colors.transparent; 
canvas.drawCircle(backgroundOffset, backgroundRadius, paint);

/*
drawCircle is recorded with the paint reference (#123 here) and the color transparent.
invocations: [MethodInvocation(#drawCircle, Paint#123(Colors.transparent))] 
*/

// Draw border.
paint..color = Colors.red; // Red for simplicity here

/*
The paint object is mutated, which ends up modifying the invocation list:
invocations: [MethodInvocation(#drawCircle, Paint#123(Colors.red))] 
*/

canvas.drawCircle(borderOffset, borderRadius, paint);

/*
drawCircle is recorded with the same paint reference (#123 here) and the color red.
invocations: [
  MethodInvocation(#drawCircle, Paint#123(Colors.red)),
  MethodInvocation(#drawCircle, Paint#123(Colors.red)),
] 
*/

So the test:

expect(
  radioFinder,
  paints
    ..circle(color: Colors.transparent)
    ..circle(color: Colors.red),
);

fails and

expect(
  radioFinder,
  paints
    ..circle(color: Colors.red)
    ..circle(color: Colors.red),
);

passes.

That's why I changed it to not reuse the same Paint


Having said this, do my changes still make sense? Or was there a better thing to do I missed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the thorough explanation here! Is the reason why the invocation list changes simply because it is holding a weak reference to the Paint or something like that? I'm just trying to understand the details here. Maybe we can explain this in the docs somewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, if I apply the same example (using a transparent circle for the background and a red circle for the border) the recorded invocation list was:

// invocations: [
  MethodInvocation(#drawCircle, Paint#123(Colors.red)),
  MethodInvocation(#drawCircle, Paint#123(Colors.red)),
] 

The tests

expect(
  radioFinder,
  paints.circle(color: Colors.red),
);

successfully passed.

However, after my modification, the invocation list changed to:

// invocations: [
  MethodInvocation(#drawCircle, Paint#123(Colors.transparent)),
  MethodInvocation(#drawCircle, Paint#123(Colors.red)),
] 

Consequently, the tests failed until I updated them to:

expect(
  radioFinder,
  paints
    ..circle(color: Colors.transparent)
    ..circle(color: Colors.red),
);

Does that clear your question?


Maybe we can explain this in the docs somewhere.

Sure, where did you picture the doc to be? A comment in paint method in _RadioPainter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hello everyone,

Does my explanation address your questions? How do you envision the documentation?

It seems a bit odd to me to include comments about the tests in the paint method of _RadioPainter. What are your thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry for the delay here. I think this PR is fine as-is. I've made a note about this in the docs for paints here: #171180

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this pr is fine as-is, but I think we should fix the mock canvas to be able to handle this. we can continue the discussion in other pr

@ValentinVignal
ValentinVignal force-pushed the flutter/fix-radio-painter branch from 5d0fd66 to 512adb0 Compare June 11, 2025 14:21
@ValentinVignal
ValentinVignal force-pushed the flutter/fix-radio-painter branch from 512adb0 to 4876422 Compare June 11, 2025 15:33

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM but left a question below. Thanks for improving the codebase 👍

final Color effectiveInactiveColor =
inactiveColor ?? defaults.fillColor!.resolve(inactiveStates)!;
// TODO(ValentinVignal): ADd backgroundColor to RadioThemeData.
// TODO(ValentinVignal): Add backgroundColor to RadioThemeData.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nIcE 👍

Material.of(tester.element(find.byType(Radio<int>))),
paints
..rect()
..circle(color: Colors.transparent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the thorough explanation here! Is the reason why the invocation list changes simply because it is holding a weak reference to the Paint or something like that? I'm just trying to understand the details here. Maybe we can explain this in the docs somewhere.

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

Material.of(tester.element(find.byType(Radio<int>))),
paints
..rect()
..circle(color: Colors.transparent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry for the delay here. I think this PR is fine as-is. I've made a note about this in the docs for paints here: #171180

@justinmc justinmc added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 25, 2025
@auto-submit
auto-submit Bot added this pull request to the merge queue Jun 25, 2025
Merged via the queue into flutter:master with commit a5a3ab3 Jun 25, 2025
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jun 25, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 28, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 28, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 1, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 1, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 2, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 2, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 2, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 2, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 2, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 4, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 4, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 4, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 5, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 5, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 7, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 7, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 7, 2025
mboetger pushed a commit to mboetger/flutter that referenced this pull request Jul 21, 2025
While working on flutter#168787 as a
follow-up of flutter#169415, I noticed
that the tests introduced in
flutter#169415 were incorrect. However,
because of the way the `Radio` implements `paint` and reuses 1 painter,
it was not possible to properly assert the paints in the test.

This PR fixes it

## Pre-launch Checklist

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

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants