-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Adds radio group widget #167363
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
Adds radio group widget #167363
Conversation
|
The pr right now only support material radio, or cupertino radio built as a result of Radio.adaptive. If we want to support CupertinoRadio, we have to move some of the Radio logic and RadioGroup to the widget layer. that is something that we can do as a follow up |
Piinks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First impression looking at that code snippet ➡️ 💯
Really nice simplification of the API, like seriously.
IMO we should just deprecate the old way - unless is there a way someone could be using Radios that is not compatible with RadioGroup?
We won't be able to assert it is configured right until runtime, and the code is so much nicer in this new way you have designed - and more accessible! I don't think dart fix will be able to help here, so a migration guide would also be helpful.
| /// same value type. To use this widget, the [Radio.groupValue] must leave as | ||
| /// null. Otherwise, an assertion error is thrown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// same value type. To use this widget, the [Radio.groupValue] must leave as | |
| /// null. Otherwise, an assertion error is thrown. | |
| /// same value type. To use this widget, the [Radio.groupValue] must remain | |
| /// null, for the RadioGroup to manage. Otherwise, an assertion error is thrown. |
| /// same value type. To use this widget, the [Radio.groupValue] must leave as | ||
| /// null. Otherwise, an assertion error is thrown. | ||
| /// | ||
| /// This widget internal builds a [RadioGroupPolicy] to provide an accessible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// This widget internal builds a [RadioGroupPolicy] to provide an accessible | |
| /// This widget internally builds a [RadioGroupPolicy] to provide an accessible |
|
|
||
| /// A container that provides semantics and keyboard navigation. | ||
| /// | ||
| /// This widget treats all [Radio]s in the subtree with the same generic type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it assert this statement?
| required this.value, | ||
| required this.groupValue, | ||
| required this.onChanged, | ||
| this.groupValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we deprecate groupValue and really encourage folks to migrate to RadioGroup so that folks are using best practices for accessibility?
The fact that this also reduces verbosity is a huge win.
| _group = context.dependOnInheritedWidgetOfExactType<_RadioGroupStateScope<T>>()?.state; | ||
| assert( | ||
| _group == null || widget.groupValue == null, | ||
| 'A RadioGroup can not wrap Radio widget with a non null groupValue. ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message was a little confusing, but we can revisit - I know you asked for early feedback.
| const Radio({ | ||
| super.key, | ||
| required this.value, | ||
| required this.groupValue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For where we assert against the new and old API choice, I think some of them are not yet considering that both groupValue and onChanged are required for the old path - but I did not look too closely since this is just an initial pass. We can check later.
justinmc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to think of edge cases:
- What about programmatically selecting a radio button? Especially after the first build, where initialValue gets more complicated.
- What if the developer wants to do some calculation at the time that a radio button is tapped before deciding whether to select it or not?
- Are we sure that selecting multiple radio buttons is completely disallowed? Maybe that's a good thing actually.
Typically I like the purely declarative approach where the app developer controls which button is selected by calling setState (similar to React). The old way did follow this approach even if it was verbose and a little awkward. The new way does not if I'm not mistaken, because a radio button will automatically be checked when it is tapped, right? So my instincts make me wary that the new way will cause trouble in certain situations.
Maybe we can do a purely declarative RadioGroup and get the best of both worlds? Something like:
int? selection;
@override
Widget build(BuildContext context) {
return RadioGroup<int>(
value: selection,
onChange: (int? value) {
setState(() {
selection = value;
});
child: Column(
children: <Widget>[
Radio<int>(value: 0),
Radio<int>(value: 1),
],
),
);
}| : super(); | ||
|
|
||
| /// The initial selection on the [Radio] of the matching [Radio.value]. | ||
| final T? initialGroupValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern makes me hesitate, even though I know we've done it elsewhere in the framework. I worry that there are cases where it gets confusing whether an initialValue should be applied or not, like rebuilding the widget, changing the initialValue after user interaction, moving the widget, etc.
I like the idea, I do think this not only makes it more flexible but also simply some of the implementation. I will make the change.
Not according to material guide, but if they really want to they can do something like so I think they can still achieve the same behavior. If this is a very common case, we can consider factor out a pure UI with minimal gesture RadioBase widget that basically just build a selected and unselected state without the value and groupvalue logic, which can be done in a separate pr. |
If we go with declarative API @justinmc mentioned, I think they should be able to migrate for various use cases. I also can't find a reasonable use case that would prefer the old API. I agree we should deprecate it. |
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> This is in prepare for #167363 The goal for this refactoring is to make sure the new RadioGroup widget can be used for both CupertinoRadio and Radio, and thus the widget has to be in widget layer to avoid duplicating code. Currently CupertinoRadio and Radio shares ToggleableStateMixin in widget layer. but the mixin itself doesn't contains enough data to implement radioGroup. Therefore, I moved more share logic from CupertinoRadio and Radio to widget layer and created a RadioBase widget. ## 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. 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
|
close in the favor of #168161 |
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> previous #167363 I have to factor out abstract class for RadioGroupRegistry and RadioClient which are subclassed by RadioGroupState and RawRadio respectively. I have to do this because the RadioListTile that has 2 focusnode one for listTile and one for the radio it builds. The issue is that RadioGroup's keyboard shortcut has to tightly couples with the focus node of each radio, but the radioListtile has to mute the radio's focusnode so it can act as one control under keyboard shortcut https://github.com/flutter/flutter/blob/d582b358092d736c79b43a43e9b68b83a779cb6c/packages/flutter/lib/src/material/radio_list_tile.dart#L484 Therefore i abstract the out the logic of RadioGroup so that another widget can participate by implementing the interface. fixes #113562 migration guide: https://github.com/flutter/website/pull/12080/files ## 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. 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
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> This is in prepare for flutter#167363 The goal for this refactoring is to make sure the new RadioGroup widget can be used for both CupertinoRadio and Radio, and thus the widget has to be in widget layer to avoid duplicating code. Currently CupertinoRadio and Radio shares ToggleableStateMixin in widget layer. but the mixin itself doesn't contains enough data to implement radioGroup. Therefore, I moved more share logic from CupertinoRadio and Radio to widget layer and created a RadioBase widget. ## 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. 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
fixes #113562
This pr introduce a new widget RadioGroup
It does two things:
If for some reason, one only wants to have (2), they can use RadioGroupPolicy.
Before this change developers have to implement
After this change
They also get keyboard behaviors match Accessibility standard
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.