-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Closed
Labels
P3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to FlutterA detailed proposal for a change to Flutterframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.r: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team
Description
As explained in a Decoding Flutter video, the WidgetStateProperty class (which until recently was called MaterialStateProperty) allows for styling based on multiple different factors without an enormous parameter list.
Problem
Let's look at some common use cases:
- Check
WidgetStates one at a time - Check multiple
WidgetStates to see if any of them match - Check multiple
WidgetStates to see if all of them match
code snippet (click to collapse)
// one at a time
WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return Colors.grey;
} else if (states.contains(WidgetState.error)) {
return Colors.red;
} else if (states.contains(WidgetState.pressed)) {
return Colors.blueAccent;
} else if (states.contains(WidgetState.focused)) {
return Colors.blue;
} else if (states.contains(WidgetState.hovered)) {
return Colors.blueGrey;
}
return Colors.black;
});
const activeStates = [
WidgetState.pressed,
WidgetState.hovered,
WidgetState.focused,
];
// see if any match
WidgetStateProperty.resolveWith<Color>((states) {
if (activeStates.any(states.contains)) {
if (states.contains(WidgetState.error) {
return Colors.red;
}
return Colors.blue;
}
return Colors.black;
});
// see if all match
WidgetStateProperty.resolveWith<Color>((states) {
if (states.containsAll(activeStates)) {
if (states.contains(WidgetState.error) {
return Colors.red;
}
return Colors.blue;
}
return Colors.black;
});Pros
- effective
Cons
- ugly
With 8 different enum values, we have 2⁸ different Set<WidgetState> possibilities, so finding a WidgetPropertyResolver implementation that isn't messy or limited in scope is challenging.
Solutions
Set pattern matching
code snippet
// one at a time
WidgetStateProperty.resolveWith<Color>((states) => switch (states) {
{WidgetState.disabled} => Colors.grey,
{WidgetState.error} => Colors.red,
{WidgetState.pressed} => Colors.blueAccent,
{WidgetState.focused} => Colors.blue,
{WidgetState.hovered} => Colors.blueGrey,
_ => Colors.black,
});
// see if any match
WidgetStateProperty.resolveWith<Color>((states) => switch (states) {
{WidgetState.pressed || WidgetState.hovered || WidgetState.focused, WidgetState.error} => Colors.red,
{WidgetState.pressed || WidgetState.hovered || WidgetState.focused} => Colors.blue,
_ => Colors.black,
});
// see if all match
WidgetStateProperty.resolveWith<Color>((states) => switch (states) {
{WidgetState.pressed, WidgetState.hovered, WidgetState.focused, WidgetState.error} => Colors.red,
{WidgetState.pressed, WidgetState.hovered, WidgetState.focused} => Colors.blue,
_ => Colors.black,
});Pros
- super clean & readable 😍
Cons
- This isn't supported in the Dart language 😢
There's an open issue for it though; maybe we could just keep our fingers crossed.
Convert to Map
T resolveWithMap<T>(T function(Map<WidgetState, bool> resolve)) => resolveWith((states) {
final stateMap = {for (final state in WidgetStates) state: states.contains(state)};
return resolve(stateMap);
});code snippet
// one at a time
WidgetStateProperty.resolveWithMap<Color>((stateMap) => switch (stateMap) {
{WidgetState.disabled: true} => Colors.grey,
{WidgetState.error: true} => Colors.red,
{WidgetState.pressed: true} => Colors.blueAccent,
{WidgetState.focused: true} => Colors.blue,
{WidgetState.hovered: true} => Colors.blueGrey,
_ => Colors.black,
});
// see if any match
WidgetStateProperty.resolveWithMap<Color>((stateMap) {
switch (stateMap) {
case {WidgetState.pressed: true}:
case {WidgetState.hovered: true}:
case {WidgetState.focused: true}:
if (states.contains(WidgetState.error) {
return Colors.red;
}
return Colors.blue;
default:
return Colors.black;
}
});
// see if all match
WidgetStateProperty.resolveWithMap<Color>((stateMap) {
switch (stateMap) {
case {WidgetState.pressed: false}:
case {WidgetState.hovered: false}:
case {WidgetState.focused: false}:
return Colors.black;
case {WidgetState.error: true}:
return Colors.red;
default:
return Colors.blue;
}
});Pros
- Works with the current pattern matching features
- The ability to check for either
trueorfalsehelps avoid repetition
Cons
- A tinge of sadness—in most situations, it's not quite as concise as
Setpattern matching would be
Enhanced enum
Instead of just trying to improve the way we parse the set, we could also try to improve WidgetState directly.
enum WidgetState with SpicyMixin { ... }code snippet
// one at a time
WidgetStateProperty.map<Color>({
WidgetState.disabled: Colors.grey,
WidgetState.error: Colors.red,
WidgetState.pressed: Colors.blueAccent,
WidgetState.focused: Colors.blue,
WidgetState.hovered: Colors.blueGrey,
WidgetState.any: Colors.black, // "any" is a static const member, not an enum value
});
final anyActive = WidgetState.pressed | WidgetState.hovered | WidgetState.focused;
// see if any match
WidgetStateProperty.map<Color>({
anyActive & WidgetState.error: Colors.red,
anyActive: Colors.blue,
~anyActive: Colors.black,
});
final allActive = WidgetState.pressed & WidgetState.hovered & WidgetState.focused;
// see if all match
WidgetStateProperty.map<Color>({
allActive & WidgetState.error: Colors.red,
allActive: Colors.blue,
~allActive: Colors.black,
});Pros
- Even more concise and readable than
Setpattern matching - Even more flexible than
Mappattern matching - Syntax is similar to named arguments
Cons
- The syntax still feels kinda wonky, though.
Metadata
Metadata
Assignees
Labels
P3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to FlutterA detailed proposal for a change to Flutterframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.r: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-frameworkOwned by Framework teamOwned by Framework teamtriaged-frameworkTriaged by Framework teamTriaged by Framework team