-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add lints to help users avoid common Widget anti-patterns. #57956
Comments
These ideas are great. Do you imagine folks would want to opt in to some but not all of these advices? To what extent can/should we bundle them in a single rule? 1-3 seem like a natural bundle... 4 might deserve it's own. Thoughts on names? Also, motivating examples (ideally from the wild) would be welcome for documentation too. |
I could imagine cases when teams might define conventions that would negate some of 1-3 but not others, so I'd leave them as separate lints. Perhaps it's just my unfamiliarity with flutter, but I need more concrete information to understand 4. |
4 is the most complex one. Container is basically a de-sugaring for a number of simple combinations of nested padding, alignment, and decoration widgets. Think of it like "div" in CSS.
is identical to Container(alignment: alignment, padding: padding, child: widget) Here is the Build method that converts the parameters of a container into the desugaring. To be safe, you could only suggest the lints if the underlying widgets are exactly in the same order they are listed in the build method. Widget build(BuildContext context) {
Widget current = child;
if (child == null && (constraints == null || !constraints.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
if (alignment != null)
current = Align(alignment: alignment, child: current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints, child: current);
if (margin != null)
current = Padding(padding: margin, child: current);
if (transform != null)
current = Transform(transform: transform, child: current);
return current;
}
|
flutter/flutter#1220 |
Regarding 4 (Using multiple widgets to do something Container can do) there shouldn't be a lint if the expression is const because |
Spinning up on analyses but would appreciate input on naming. Needless to say more ideas welcome too!
|
Is it idiomatic Flutter to prefer
|
Is this generalizable at all? For /cc @InMatrix |
I agree: it seems like 2 and 3 could be generalized.
Is there a term for widgets that control the display (or behavior) of child widgets but are not useful when there are no child widgets?
Again, seems like there are lots of cases like this that might want to be caught by the same lint. For example, if there were a |
3 would need a lot of special-casing, as well as Transitions. Like, should we flag an |
Thanks @Levi-Lesches. It would be ideal if this generalizes but it may just be a bag of special cases. @Hixie: any thoughts? |
I think it's worth pointing out that even if we don't flag every single anti-pattern, special casing may be enough -- especially since these are just lints and not a part of the language itself. The most common anti-patterns I've seen are empty |
@Levi-Lesches: any concrete examples you can share would be greatly appreciated. Thanks a million for the input! |
Sure, although this isn't the exact code. It went something like this: @override
Widget build(BuildContext context) {
// I think that with list comprehension, we really don't need functions like this.
// If you're not using a cached widget, just return a widget tree already.
// I would strongly advocate for that as a lint.
final Widget firstWidget = Padding(/* stuff */); // this is going in a padded column
// above, plus align shouldn't be used in a row when you can use mainAxisAlignment. See below
final Widget secondWidget = Padding(child: Align(/* stuff */));
final Widget simpleWidget = Container(child: Placeholder()); // no need for container
// really should be in its own widget, but that's subjective
final Widget veryComplicatedWidget = Placeholder();
return Column(
// notice how vertical padding in widgets should be replaced with SizedBox here.
// keeps the padding simple and visible
children: [
Row(
// should use alignment parameters instead of manual aligns and pads
children: [firstWidget, secondWidget],
),
simpleWidget,
veryComplicatedWidget,
]
);
// again, all we did here was define widgets and return them. No await, no adding to lists, nothing.
// easily could just be a widget tree with fat arrow. Most of Flutter team code is like this too.
} As I said, not only is this horrible to look at, but it can actually lead to un-responsive design by misleading the developer (and especially anyone else working on the code). |
#57147 would be interesting if we solved it with an annotation in some sort of general sense — "this constructor is a no-op if called with only this argument", or "this constructor is a no-op if none of these arguments are set" or something. #57148 we can solve with #57149 is sketchy. Usually padding on a widget is different than padding around a widget, and we can't easily know which is right. That might be a better feature for an IDE tool like the devtools inspector, some sort of "possible problem auditor". #57150 is not something I would recommend. Generally, it's an iota more efficient to use the widgets directly rather than Container, so I'm reluctant to encourage it. It's a taste thing at best. #57151 we have a whole list of these in the flutter issue tracker somewhere. |
Solving |
Wait so people are just putting blank
I like this idea a lot. How about something like: const Center({@noopWithOnly this.padding}); or const Center({@noopWithout this.child}); Of course, we can change "noop" to something more beginner-friendly, like "useless" or something. But I think in the parameter declaration, like |
I've seen examples of |
An "unneeded container" example in the wild: |
I'd personally like to see this one |
Hey everyone! Just some input on the padding lint proposed here. I've not completely read the entire issue so I'm sorry if I repeat something here. DCM already has a padding lint called avoid-wrapping-in-padding.
Although I agree with this statement posted above, I just recently created an unnecessary issue flutter/flutter#150924 that was about giving widgets like |
Motivation from a Flutter user group post: "I've been seeing this a lot with new devs. Don't needlessly use Containers. Wrapping a widget in Container and setting no other parameters does absolutely nothing but make your code more complicated."
These lints could help Flutter examples on the web be more idiomatic with less unneeded boilerplate.
Initial list of lints with obvious quick fixes we could add:
no_state_constructor
#58075setState
(Initial frame based timeline flame chart. flutter/devtools#1336 (comment))The text was updated successfully, but these errors were encountered: