I have realized that it is possible to create widgets using plain functions instead of subclassing StatelessWidget. An example would be this:
Widget functio
I've been researching on this issue for the past 2 days. I came to the following conclusion: it is OKAY to break down pieces of the app into functions. It's just ideal that those functions return a StatelessWidget
, so optimisations can be made, such as making the StatelessWidget
const
, so it doesn't rebuild if it doesn't have to.
For example, this piece of code is perfectly valid:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
const MyWidgetClass(key: const Key('const')),
MyWidgetClass(key: Key('non-const')),
_buildSomeWidgets(_counter),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _buildSomeWidgets(int val) {
print('${DateTime.now()} Rebuild _buildSomeWidgets');
return const MyWidgetClass(key: Key('function'));
// This is bad, because it would rebuild this every time
// return Container(
// child: Text("hi"),
// );
}
}
class MyWidgetClass extends StatelessWidget {
const MyWidgetClass({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('${DateTime.now()} Rebuild MyWidgetClass $key');
return Container(
child: Text("hi"),
);
}
}
The use of function there is perfectly fine, as it returns a const StatelessWidget
. Please correct me if I'm wrong.
TL;DR: Prefer using classes over functions to make reusable widget-tree.
EDIT: To make up for some misunderstanding: This is not about functions causing problems, but classes solving some.
Flutter wouldn't have StatelessWidget if a function could do the same thing.
Similarly, it is mainly directed at public widgets, made to be reused. It doesn't matter as much for private functions made to be used only once – although being aware of this behavior is still good.
There is an important difference between using functions instead of classes, that is: The framework is unaware of functions, but can see classes.
Consider the following "widget" function:
Widget functionWidget({ Widget child}) {
return Container(child: child);
}
used this way:
functionWidget(
child: functionWidget(),
);
And it's class equivalent:
class ClassWidget extends StatelessWidget {
final Widget child;
const ClassWidget({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: child,
);
}
}
used like that:
new ClassWidget(
child: new ClassWidget(),
);
On paper, both seem to do exactly the same thing: Create 2 Container
, with one nested into the other. But the reality is slightly different.
In the case of functions, the generated widget tree looks like this:
Container
Container
While with classes, the widget tree is:
ClassWidget
Container
ClassWidget
Container
This is important because it changes how the framework behaves when updating a widget.
By using functions to split your widget tree into multiple widgets, you expose yourself to bugs and miss on some performance optimizations.
There is no guarantee that you will have bugs by using functions, but by using classes, you are guaranteed to not face these issues.
Here are a few interactive examples on Dartpad that you can run yourself to better understand the issues:
https://dartpad.dev/1870e726d7e04699bc8f9d78ba71da35
This example showcases how by splitting your app into functions,
you may accidentally break things like AnimatedSwitcher
https://dartpad.dev/a869b21a2ebd2466b876a5997c9cf3f1
This example showcases how classes allow more granular rebuilds of the
widget tree, improving performances
https://dartpad.dev/06842ae9e4b82fad917acb88da108eee
This example showcases how, by using functions, you expose yourself
to misusing BuildContext and facing bugs when using InheritedWidgets (such as Theme or providers)
Here's a curated list of the differences between using functions and classes:
showDialogs
& similar)ClassWidget
in the widget-tree showed by the devtool, which
helps understanding what is on screenBuilder
, your errors won't have a helpful nameOverall, it is considered a bad practice to use functions over classes for reusing widgets because of these reasons.
You can, but it may bite you in the future.
When you are calling the Flutter widget make sure you use the const keyword. For example const MyListWidget();
Widgets returned by functions are rebuilt every time the widget tree is rebuilt, whether they contain a state or not.
However, stateless or stateful widgets will only be rebuilt (just them) in that widget tree if the state they contain changes.
It is advised to extract the widgets to their separate classes to improve the performance of your app. Minimize how many widgets are rebuilt...
There was a Big difference between what functions does and what class does.
Lets I will explain it from very scratch.