You should always try to use const
when possible, especially if you're creating a Flutter app. Constant constructors are very important because they're used to "cache" widgets so that they won't be unnecessarily rebuilt.
Imagine you had list of 10k items in your Flutter app:
class Example extends StatelessWidget {
const Example();
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My items:'),
ListView.builder(
shrinkWrap: true,
itemCount: 10000,
itemBuilder: (_, i) => SomeComplexWidget(
value: i,
),
),
],
);
}
}
There are a lot of factors that could trigger a widget rebuild but let's assume that you simply rotated your device screen a few times. With this code...
SizedBox(
width: 100,
height: 400,
child: Example(),
),
... you aren't using a const
constructor so you're rebuilding Example
on every rebuild (= you're also unnecessarily rebuilding the Column
, the ListView
, and re-rendering a few SomeComplexWidget
). ListView.builder
is a lazy initializer so it will save you a bit but you still don't want it to always be rebuilt.
If you used a Column()
or a plain ListView()
constructor for your list, it would have affected performance even more (more jank and thus more skipped frames). If you did this instead...
SizedBox(
width: 100,
height: 400,
child: const Example(),
),
... you would "cache" the Example
widget (meaning that it will be built only once). As you can guess, this is a very simple but important performance optimization you can do. Using constant constructors is not just my word but it's officially recommended by the docs:
Note that if your widget contained something that depends on InheritedWidget
, the const
keyword wouldn't have effect (otherwise, the widget couldn't "listen" for changes on the inherited widget".
Bonus
In Flutter, you can also cache widgets "manually". When you cannot use const
, you can use late
(or late final
) inside the state to cache the widget. Look at this example:
class OtherExample extends StatelessWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
MyInterestsList(
data: interests,
),
]
);
}
}
You want MyInterestsList
to be cached because it never changes but const
doesn't work here (since interests
is not a compile-time) constant. When you can't use const
but you still want to cache a widget, do it manually:
class OtherExample extends StatefulWidget {
final List<Interests> interests;
const OtherExample({
required this.interests,
});
@override
_OtherExampleState createState() => _OtherExampleState();
}
class _OtherExampleState extends State<OtherExample> {
late Widget myInterests = MyInterestsList(
data: widget.interests,
);
@override
void didUpdateWidget(OtherExample oldWidget) {
super.didUpdateWidget(oldWidget);
// This makes sure that the interests list is updated ONLY when
// it actually changes
if (widget.interests != oldWidget.interests) {
myInterests = MyInterestsList(
data: widget.interests,
);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('My list'),
myInterests,
]
);
}
}
Thanks to late
you're caching your widget inside the state class. Thanks to the didUpdateWidget
override, the MyInterestsList
widget will only be rebuilt when the list changes. If the list never changes, MyInterestsList
will never be rebuilt.