A collection of common issues to cover whenever reviewing Dart or Flutter code. These comments follow the Conventional Comments structure.
Problems that must be addressed.
initState
firstissue: Avoid implementing this method before calling super.initState()
.
suggestion: Call super.initState
before any other logic in your Stateful Widget.
Implementations of this method should start with a call to the inherited method, as in super.initState()
.
dispose
lastissue: Avoid calling super.dispose()
before implementing this method.
suggestion: Call super.dispose
after any other logic in your Stateful Widget.
Implementations of this method should end with a call to the inherited method, as in super.dispose()
.
issue: Avoid using dynamic type where possible.
suggestion: Consider using stronger typing rather than relying on dynamic
This language feature offers the following benefits:
See also:
https://dart.dev/guides/language/type-system
issue: Avoid using var/final when possible.
suggestion: Consider using const over var/final.
The const keyword serves as an indicator that tells the compiler that, “for this variable, it will never change in the lifetime of this code, so create just one copy of it and where ever it is mentioned, just reference back to the one copy you already created and don’t create a new one.”
See also:
https://www.youtube.com/watch?v=DCKaFaU4jdk
https://medium.com/flutter-community/the-flutter-const-keyword-demystified-c8d2a2609a80
issue: Avoid returning widgets from helper methods.
suggestion: Prefer to either inline widgets or extract them into another widget.
Extracting widgets to a method is considered a Flutter anti-pattern, because when Flutter rebuilds the widget tree, it calls the function all the time, making more processor time for the operations, and potentially short-circuiting hot reload.
There are a number of advantages to extracting UI as a widget rather than using a helper method:
It is also a good practice to create private widgets for UI that you would not like to expose in other places in the application. Or you could expose the widget for tests using the @visibleForTesting
decorator. That way you can test the component but if you try to use it in the application, you will see a warning.
See also:
https://flutter.dev/docs/perf/rendering/best-practices#controlling-build-cost
https://www.youtube.com/watch?v=IOyq-eTRhvo
https://stackoverflow.com/a/53234826
Non-blocking improvements.
suggestion: Consider passing dependencies in constructor rather than instantiating them in the class.
Example:
class NetworkHelper {
NetworkHelper(
this.url, {
http.Client? client,
}) {
this.client = client ?? http.Client();
}
late final http.Client client;
final Uri url;
Future getData() async {
http.Response response = await client.get(url);
if (response.statusCode == 200) {
String res = response.body;
final data = jsonDecode(res).cast<Map<String, dynamic>>();
return data;
} else {
print(response.statusCode);
}
}
///...rest of class
}
Passing in the client as a dependency will allow you to easily mock the client when writing tests.
See also:
https://flutter.dev/docs/cookbook/testing/unit/mocking
suggestion: Consider avoiding print
calls in production.
See also:
https://dart-lang.github.io/linter/lints/avoid_print.html
suggestion: Consider setting environment variables (such as URLs) using an environment configuration.
See also:
suggestion: Consider making variables private using an underscore and improve readability.
A public member can be accessed from outside the class. That means that if something goes wrong with a public field, the culprit could be anywhere. Additionally, using underscores for private variables improves the readability of your code by allowing users to differentiate private class variables from function variables.
See also:
https://softwareengineering.stackexchange.com/questions/143736/why-do-we-need-private-variables
suggestion: Avoid using ListView.shrinkWrap
. Consider instead using a CustomScrollView
+ SliverList
Example:
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Container(),
childCount: someObject.length,
),
),
],
),
According to the Flutter documentation, using shrinkWrap in lists is expensive performance-wise and should be avoided, since using slivers is significantly cheaper and achieves the same or even better results.
See also:
https://github.com/dart-lang/linter/issues/3496
suggestion: Prefer using Border.fromBorderSide
over Border.all
for performance, since the former is a const
constructor
Example:
class BorderWidget extends StatelessWidget {
final Widget child;
const BorderWidget({
Key? key,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
border: const Border.fromBorderSide(BorderSide(
color: const Color(0xFF000000),
width: 1.0,
style: BorderStyle.solid,
)),
child: child,
);
}
}