Description
Today, a const
constructor for class Foo
can be invoked as new Foo()
, const Foo()
or simply Foo()
.
In the last case, the invocation defaults to new Foo()
unless the expression is in a const
context, in which case it can only be const
, so it defaults to const
.
In the case of Flutter widget trees, the defaulting to new Foo()
is the wrong choice. It would be better to default to const Foo()
if at all possible.
There are several lints that try to help the developer work around the lack of const-by-default:
prefer_const_constructors
to encourage the developer to putconst
at each constructor in the widget tree expressionprefer_const_literals_to_create_immutables
to encourage the developer to putconst
on some subexpressions even when the constructor is notconst
.unnecessary_const
to help the developer removeconst
from a const context.
If the developer has a const
expression and changes some deep sup-expression to be non-const, the expression is now an error. The developer removes the high const
to fix the error. The lints now encourage adding const
all along the side-trees of the spine of the expression tree leading to the original edit.
Example
As an example of this experience, copy the following program into dartpad.
import 'package:flutter/material.dart';
/// Flutter code sample for [Divider].
void main() => runApp(const DividerExampleApp());
class DividerExampleApp extends StatelessWidget {
const DividerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Divider Sample')),
body: const DividerExample(),
),
);
}
}
class DividerExample extends StatelessWidget {
const DividerExample({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Column(
children: <Widget>[
Expanded(
child: ColoredBox(
color: Colors.amber,
child: Center(
child: Text('Above'),
),
),
),
Divider(
height: 20,
thickness: 5,
indent: 20,
endIndent: 0,
color: Colors.black,
),
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
'Subheader',
textAlign: TextAlign.start,
),
),
Expanded(
child: ColoredBox(
color: Colors.blue,
/*54*/ //color: Theme.of(context).colorScheme.primary,
child: Center(
child: Text('Below'),
),
),
),
],
),
);
}
}
Now use a computed color: uncomment line 54 and comment-out line 53.
You will see 7 errors for 'invalid const value'.
The remedy is to remove the const
at line 26.
You will now see 9 lint warnings to prefer const
.
The remedy is to add const
in 4 places.
The lints have quick-fixes to help with this process, but pushing const
around the code would be completely unnecessary if the widget constructors defaulted to const
whenever possible.
Questions
Should const-by-default be the default behaviour? This would break identical(Object(), Object())
. One can always 'escape' the behaviour with an explicit new
, but this idea is probably too breaking.
If const-by-default is not the default behaviour, should the language add an opt-in syntax, e.g. putting the pseudo-keyword prefer
in front of const
, i.e.
class Foo {
final List<Widget> children;
final Color color;
prefer const Foo({this.children = const [], this.color = Colors.black});
}
The opt-in could be via a class modifier: const class Foo { }
. This makes all constructors const-by-default.
A class or constructor opt-in would nicest for Flutter - the preference for const
is really an implementation detail of Flutter, so should be 'batteries included' as much as possible.
Should a const-by-default constructor that can't be const
because of one argument make the other argument expressions be const if possible? e.g. should Foo(color: computed(), children: [Foo()])
make the children:
list be const?
Should a non-const constructor (or for that matter, a static method) be able to impose a const-by-default context on an argument by adding const
or prefer const
to the parameter declaration?
Another option would be to have syntax to introduce a const-by-default context, say, const?
. In the big example, writing return const? Center(...
would infect the whole expression with const-by-default. I think this is worse than the above ideas, mainly because the Flutter developer still has to do something.