flutter
flutter copied to clipboard
Make Checkbox adaptive without delegating to `CupertinoCheckbox`
Currently, Checkbox.adaptive delegates to CupertinoCheckbox when the current platform is iOS or macOS. This PR serves to make Material Checkbox configure to a Cupertino look and feel when the Checkbox.adaptive constructor is used on iOS or macOS.
Currently, delegating to CupertinoCheckbox means that many parameters to material Checkbox are ignored:
https://github.com/flutter/flutter/blob/54e66469a933b60ddf175f858f82eaeb97e48c8d/packages/flutter/lib/src/material/checkbox.dart#L109-L115
This PR allows for an adaptive checkbox that keeps all the parameters to the regular material Checkbox, allowing for the same levels of customization, thus fixing issues like #134917.
https://github.com/flutter/flutter/issues/134917#issuecomment-1728605932 provides the value proposal behind this PR.
Before:
After:
code sample
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// Flutter code sample for [Checkbox].
void main() => runApp(const CheckboxExampleApp());
class CheckboxExampleApp extends StatelessWidget {
const CheckboxExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Checkbox Sample')),
body: const Center(
child: CheckboxExample(),
),
),
);
}
}
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool? isChecked = true;
@override
Widget build(BuildContext context) {
Color getColor(Set<WidgetState> states) {
const Set<WidgetState> interactiveStates = <WidgetState>{
WidgetState.pressed,
WidgetState.hovered,
WidgetState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.blue;
}
return Colors.red;
}
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text('Cupertino Checkbox:'),
CupertinoCheckbox(
checkColor: Colors.white,
tristate: true,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
const Text('Material Checkbox:'),
Checkbox(
checkColor: Colors.white,
tristate: true,
fillColor: WidgetStateProperty.resolveWith(getColor),
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
const Text('Adaptive Checkbox'),
Checkbox.adaptive(
checkColor: Colors.white,
tristate: true,
fillColor: WidgetStateProperty.resolveWith(getColor),
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
],
);
}
}
Related PR: #130425
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.
- [ ] I followed the breaking change policy and added Data Driven Fixes where supported.
- [x] All existing and new tests are passing.
Somehow this PR doesn't trigger "Google testing", maybe mark this to be Ready to review? I think this PR change probably will break G3 and we might also need to add the Adaptation change, just like what Switch did(link).
Can you share the code snippet that creates the video above? I am curious where the red color is coming from in the material one. Is this red color set directly on the Checkbox widget? If so, should the adaptive Checkbox on iOS honor that red color?
Can you share the code snippet that creates the video above? I am curious where the red color is coming from in the material one. Is this red color set directly on the Checkbox widget? If so, should the adaptive Checkbox on iOS honor that red color?
The red color of the checkbox is because of the customization of the fillColor property to change colors depending on the WidgetState. This was done to show the customization ability with the new adaptive checkbox which did not previously exist in the former implementation that delegated to CupertinoCheckbox.
Code snippet
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// Flutter code sample for [Checkbox].
void main() => runApp(const CheckboxExampleApp());
class CheckboxExampleApp extends StatelessWidget {
const CheckboxExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Checkbox Sample')),
body: const Center(
child: CheckboxExample(),
),
),
);
}
}
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool? isChecked = true;
@override
Widget build(BuildContext context) {
Color getColor(Set<WidgetState> states) {
const Set<WidgetState> interactiveStates = <WidgetState>{
WidgetState.pressed,
WidgetState.hovered,
WidgetState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.blue;
}
return Colors.red;
}
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text('Cupertino Checkbox:'),
CupertinoCheckbox(
checkColor: Colors.white,
tristate: true,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
const Text('Material Checkbox:'),
Checkbox(
fillColor: WidgetStateProperty.resolveWith(getColor),
tristate: true,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
const Text('Adaptive Checkbox'),
Checkbox.adaptive(
fillColor: WidgetStateProperty.resolveWith(getColor),
tristate: true,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value;
});
},
),
],
);
}
}
One more thing. Maybe we should link to Checkbox.adaptive in the docs for CupertinoCheckbox under "See also".
One more thing. Maybe we should link to Checkbox.adaptive in the docs for CupertinoCheckbox under "See also".
Yes, this is what is still unclear to me. Do we want folks to migrate and stop using CupertinoCheckbox? With 2 iOS implementations of checkbox now, what happens to the cupertino one?
code snippet
Thanks for the code! Do you think the adaptive one should be red on iOS? Since it is given the same fillColor: WidgetStateProperty.resolveWith(getColor), or will the adaptive Checkbox still ignore some properties here?
Some of these discussion threads can probably be moved into the doc, Github UI is tedious for conversations. 😅
code snippet
Thanks for the code! Do you think the adaptive one should be red on iOS? Since it is given the same
fillColor: WidgetStateProperty.resolveWith(getColor), or will the adaptive Checkbox still ignore some properties here?
I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the fillColor property, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.
With the previous adaptive checkbox, fillColor would have been ignored, and so the checkbox would always have the blue color no matter what.
I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the
fillColorproperty, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.
I agree! It looks like in the video it does not reflect that red color though?
I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the
fillColorproperty, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.I agree! It looks like in the video it does not reflect that red color though?
Oh I see what you mean.
Actually I am just realizing I was supposed to upload two videos (before and after) and just uploaded one (the before). So the current video is the behavior with the previous adaptive checkbox. I'll add the second video and annotate properly.
Thanks for pointing that out, I totally missed it.