-
Notifications
You must be signed in to change notification settings - Fork 26.9k
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
Why should I ever use stateless widgets instead of functional widgets? #19269
Comments
My simple answer would be. |
Stateless Widget doesn't change its state either, right? My best argument for using StatelessWidget is to create a widget that can be used to build tons of widgets that inherit its properties. But this brings up another question. In react there are Functional components and Class based components. Functional components just build a component, like I have done with my functional widget above. Class based components can be extended (iirc) and they have state. So why would Flutter have a class based component for both Stateless and Stateful widgets? What benefit does this provide over Functional and Class based being the two methods of constructing widgets? (Where class based always has state) Is a StatefulWidget more expensive to use than StatelessWidget? |
There is nothing wrong with functions, but Widgets can be |
You also don't have the associated Element with the function. i.e. how would you put a button that navigates in the function if you don't have the BuildContext. |
That's a good point. What stops someone from passing context as an argument, though? |
There's no Element associated with a function so you're passing in someone else's BuildContext. Then you have to manually make sure you get the right BuildContext. If you had:
And your function tries to push onto that navigator, it'd fail or get the wrong one. Then you'd have to manually wrap your function in a Builder and you're more or less back to having a StatelessWidget. |
So functional components for breaking down big trees but that's about it, it sounds like. Or fast prototyping. |
Ya, the main difference is in how it links to the Element tree (in that functions don't get references to Elements themselves and won't get triggered to rebuild themselves via the Element tree). Some other examples:
|
A few other things :
In the end your only gain is 2-3 lines of code. Lines which can be generated fairly easily with a code snippet. For instance in vscode, just write |
Oh and another one : Widgets shouldn't have to care how other widgets are implemented. If you had to do Which means that if you ever need to convert your stateless widget into a stateful or the opposite then it is a breaking change. |
Thanks for your input Rémi, it is much appreciated. It seems like there are a lot of very valid reasons indeed to use Stateless & Stateful and not use a 'functional widget'. I think the breaking change issue is very significant and I think I'm mostly convinced to always use a Stateless or a Stateful widget. However, I think the number of lines problem is significant, writing code using a code snipet is not the problem here, it is reading those lines back. Also, if the 'functional widget' requires one parameter we go from five lines of code to one line of code, this is really significant when looking back through code and trying to work out what my code does (83% fewer lines of code, 54 chars vs 169, 68% fewer chars, again this is significant when looking back through code).
Flipping the question a little bit more (by the way I do love Flutter have dropped React Native for it!), is it ever ok to use a 'functional widget'? As a starter I would say it is ok privately inside a stateless or stateful widget. |
Well, if you really dislike the class syntax; it should be fairly straightforward to fix most of the previous downsides using a code generator You could write the following : // foo.dart
part 'foo.g.dart';
@stateless
Widget _MyStateless({String foo}) => Text(foo); which then generates the following // foo.g.dart
part of 'foo.dart';
class MyStateless extends StatelessWidget {
final String foo;
MyStateless({this.foo, Key key}): super(key: key);
@override
Widget build(BuildContext context) {
return _MyStateless(foo: foo);
}
} That would be a much more reasonable approach to functional widgets. The downside is that IDE shortcuts such as Would that be fine with you? |
Thanks Remi, I'll have a play, that looks promising :-) |
That would seem like a huge step in the right direction without going full JSX/DSX and is basically what I was trying to brainstorm in #19811 My current approach is to fit functional widgets only inside larger stateful/stateless class based widgets but even this isn't great for readability, and it makes my app nearly monolithic. If it never needs context or key, it stays functional, for example a The class format is really obtrusive in my opinion and if there is a way to reduce it without breaking anything else in an optional fashion would be ideal. If I were writing a framework/package I would never use shorthand, but if I'm writing an app I am shorthand all the way. This is a staple format for me _title(String text) => Text(text, style: TextStyle(fontSize: 18.0)); I will NEVER class _Title extends StatelessWidget {
_Title(this.text);
String text;
@override
Widget build(BuildContext context) {
return Text(text, style: TextStyle(fontSize: 18.0),);
}
} If I could do this, I absolutely would, especially if it exposed context and key. @stateless
_Title(String text) => Text(text, style: TextStyle(fontSize: 18.0)); And to be perfectly honest, I actually prefer that my widgets have a different syntax highlighting and style compared to built-in/pub widgets. It makes it so much easier to see what are widgets that I maintain, and what are widgets that someone else maintains. |
@twistedinferno @rrousselGit did either of you get the |
|
Just out of curiosity, why do you say And that would be fantastic and I would love to try it out and see what your solution is. |
Sorry for the delay. From @widget
Widget foo(BuildContext context, { int value, int value2 }) {
return Text('$value $value2');
} it generates the following class for you to use: class Foo extends StatelessWidget {
final int value;
final int value2;
const Foo({Key key, this.value, this.value2}) : super(key: key);
@override
Widget build(BuildContext context) {
return foo(context, value: value, value2: value2)
}
} |
I think this is solved and I'm going to close. |
Could you elaborate further? I don't exactly understand either the principle or the pseudocode. I've tried pushing onto a navigator from a widget yielding function, using a BuildContext passed from the Widget, and it works as expected. |
class Foo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: home(context),
);
}
}
Widget home(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => Navigator.of(context).pushNamed('/foo'),
child: Text('bar'),
),
),
);
} This won't work. Because To solve this issue, you'd have to write: Widget home() {
return Builder(
builder: (context) => Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => Navigator.of(context).pushNamed('/foo'),
child: Text('bar'),
),
),
),
);
} But that |
I believe @rrousselGit is describing an edge case where the context doesn't have a navigator. The problem isn't passing context to a functional widget, the problem is passing a context that is unaware of a MaterialApp. This would only be a problem in practice if you don't have any builder methods between MaterialApp and your functional widget. (I have often wondered why |
So the problem only arises if the passed context was created before the MaterialApp, which is supposed to configure the top level Navigator, correct? |
A good sums up: Classes:
Functions have:
|
@Maldus512 you got it. |
Guys, I get confused. Should I really avoid to do something like this? return ListView(
children: <Widget>[
_buildMyStatelessWidget(), // a simple Text widget
_buildMyStatelessWidget2(context), // a widget that reads some properties from context.theme
_buildMyStatefulWidget(),
]
); |
@SaeedMasoumi can you show the rest of the code for |
@WantMoreCookies _buildMyStatelessWidget() => ImageIcon(AssetImage("example.png"));
// or using const
_buildMyStatelessWidget() => const SizedBox(height: 20);
_buildMyStatelessWidget2(context) => Text(
S.of(context).some_text,
style: Theme.of(context).textTheme.title,
);
_buildMyStatefulWidget() => MyScreen(); |
@SaeedMasoumi yea so this is exactly what you want to avoid doing. Instead you will want to do this:
|
@WantMoreCookies I don't know what are the downsides of my approach and why I should use classes instead, I can even achieve hot reloading with functional widgets. |
Could somebody answer @SaeedMasoumi 's last comment? I'm also wondering why it's a bad approach. It seems way too much to create a What if instead of the code above, the class ListViewWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(children: <Widget>[
_buildMyStatelessWidget(), // a simple Text widget
_buildMyStatelessWidget2(
context), // a widget that reads some properties from context.theme
_buildMyStatefulWidget(),
]);
}
} like so |
@SaeedMasoumi @sergiuiacob1 - This and this written by @rrousselGit should help you understand why. tldr; "The framework [Flutter] is unaware of functions, but can see classes. Functions are not inserted in the Widget tree and therefore are never associated to an Element. This causes a whole new set of problems, including performance issues or state not properly disposed." |
@WantMoreCookies
Is there any example to show these performance issues? For example, what are the performance issues of the below function? SizedBox vGap4() => const SizedBox(height: 4); Maybe @rrousselGit can help. |
TD;DR: misused functions are a lot more dangerous than misused classes. So use classes Honestly, the performance aspect barely matters. Consider: bool condition;
Widget _foo();
Widget _bar();
Widget build(BuildContext context) {
return condition
? _foo()
: _bar();
} With that code, flutter is unable to understand when we switch between While classes require a bit more code, they are a lot safer. |
I know I am coming late to the party. But, that question always pops up again. I think an important aspect is that Flutter stops rebuilding if encounters an equal widget at the same position in the tree. Hence, by overriding The Flutter docs read "same instance", but in reality they mean "equal instance". So, I opened a ticket to ask the Flutter team to clarify and emphasize that in the docs: #38740 |
I have often wished for a |
@rrousselGit Would you please provide equivalent code for the Class paradigm? I thought I had this concept cleared up until reading that last comment 😓 |
I'm not sure this is the case. Wouldn't the same problem apply to classes as well? If that |
Classes will dispose the element and state when they are switched. With functions, it might happen that state leaks between succeeding trees. A password might suddenly appear somewhere else as readable text. |
So is there any difference between using a function that returns a Widget and just inlining the Widget? For (a very contrived) example: class Toggler extends StatelessWidget {
final VoidCallback onToggle;
final VoidCallback onAdvancedToggle;
const Toggler({Key key, this.onToggle, this.onAdvancedToggle}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
FlatButton(onPressed: onToggle, onLongPress: onAdvancedToggle, child: Text('On')),
FlatButton(onPressed: onToggle, onLongPress: onAdvancedToggle, child: Text('Off')),
],
);
}
} Here I'm using a couple of member variables while building the FlatButton, and it looks like a candidate for DRYing up. However making it a stateless widget doesn't help at all, since then I still lose the member variables and just have to pass the same values. So my instinct is: //...
Widget _toggleButton(String text) =>
FlatButton(onPressed: onToggle, onLongPress: onAdvancedToggle, child: Text(text));
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
_toggleButton('On'),
_toggleButton('Off'),
],
);
} Is that bad? For at least some of the points made elsewhere, I think that using a function that returns a Widget should be the same as inlining code? But then again @rrousselGit's comment that
makes me question whether I actually understand it. So I get that there are important differences between extracting a StatelessWidget and using a function. But is using a function different from inlining the code directly in the |
@hmayer00 inlining and using a function is exactly the same. The difference is between inlining/function and a proper widget-class. In the first case, Flutter aggressively tries to reuse the elements and state. In the, second case Flutter will only reuse the element and state iff the runtimeType and the key match. Consider two forms with text edits at the same location in the widget tree and switching between the forms. Do you want the edited text to spill over from the first to the second form or reset when you switch between the forms? The first is achieved with inlining/function, the later with proper subclasses or different keys. |
@derolf is on point. If that wasn't clear enough, here's a concrete example: Widget functionA() => Container()
@override
Widget build() {
return functionA()
} is equivalent to: @override
Widget build() {
return Container();
} whereas: class ClassA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
@override
Widget build() {
return ClassA()
} is equivalent to: @override
Widget build() {
return KeyedSubtree(
key: ObjectKey(ClassA),
child: Container(),
);
} |
Btw https://flutter.dev/docs/development/ui/widgets-intro#keys explains it all... |
Thanks @derolf and @rrousselGit. That's what I had thought, but things seemed tricky enough that I wanted to make sure I hadn't missed something. And you and others have in previous comments certainly convinced me of the value of widget classes (also it helps that functional_widget_annotation takes away some of the boilerplate when convenient - thanks @rrousselGit!). Btw, to @derolf's point about keys, I'm generally averse to learning by video, but I found the intro video about keys to be extremely helpful. |
@hmayer00 To understand how Flutter works, it's quite helpful to:
|
For those who don't want to use code-generators/functional_widget, there are other alternatives. We can use "higher order functions". TD;DR instead of: Widget myWidget(String title) {
return Text(title);
}
Builder(
key: ValueKey(42),
builder: (context) {
return myWidget('hello world');
},
) you do: final myWidget = functionalWidget((BuildContext context, String title) {
return Text(title);
});
myWidget('hello world', key: ValueKey(42)); Here's the Widget Function(T props, {Key key}) functionalWidget<T>(
Widget value(BuildContext c, T props)) {
final uniqueWidgetIdentifier = UniqueKey();
return (props, {Key key}) {
var child = Builder(
key: uniqueWidgetIdentifier,
builder: (context) {
return value(context, props);
},
);
if (key != null) {
return KeyedSubtree(
key: key,
child: child,
);
}
return child;
};
} The downside is, if the props of your function are anything more complex than a class MyProps {
String title;
int count;
}
final myComplexWidget = functionalWidget<MyProps>((context, props) {
return Text('${props.title} ${props.count}');
});
myComplexWidget(
MyProps()
..title = 'Hello world'
..count = 42,
) It'd be perfect if Dart had structures/records though. |
I just use on my app
I find the There are some specific scenarios that require me to update the UI without modifying the global provider. For this I just use |
Am I understanding it right if I say @override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: const Text('Hello world', style: TextStyle(color: Colors.blue)),
);
} Can be refactored as @override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: const _MyText(),
);
}
// ...
class _MyText extends StatelessWidget {
const _MyText({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Text('Hello world', style: TextStyle(color: Colors.blue));
}
} And never as @override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: _buildMyText(),
);
}
Widget _buildMyText() {
return const Text('Hello world', style: TextStyle(color: Colors.blue));
} And it is fine for final List<Fruit> fruits;
//...
@override
Widget build(BuildContext context) {
return Column(
children: fruits.map((f) => _FruitInfo(fruit: f)).toList(),
);
} to be refactored to final List<Fruit> fruits;
//...
@override
Widget build(BuildContext context) {
return Column(
children: _buildChildren(),
);
}
List<Widget> _buildChildren() {
return fruits.map((f) => _FruitInfo(fruit: f)).toList();
} and it is fine because the inline and function implementations are no different. Or is there another way to build a |
why a normal widget without class is wrong? I think it depends if you need a different context |
There is no hard rule. It’s just best practices! The reasons were already given multiple times in this thread. |
Here is the hard and fast logic again:
|
Wish primary constructor landed on dart dart-lang/language#138 so I don't need to use functional widget to reduce LoC. |
I read whole topic and I can't understand WHY?! Why Flutter aggressively tries to reuse the elements and state? Widget functionA() => Container() And assign it to constructor parameter. Widget home(BuildContext context) {
return Scaffold(
body: Center(
child: functionA(),
),
);
} The first of all compiler initialise all objects and executes all methods/functions and create all widgets and after that Flutter will check elements and states. Then will make a decision to reuse element and render objects. And I think the same will be when I use StatelessWidget. But you say NO. Not the same. Could you describe step by step what difference? May be Flutter start check runtimeType and the key in different order? But how?! |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Dart 2 shorthand makes writing functional widgets super nice, especially for a React/Javascript heathen like myself.
Why should I ever use Stateless Widgets?
The text was updated successfully, but these errors were encountered: