Skip to content

Code Style

In general, the best guides for code style are the Effective Dart guidelines and the linter rules set up in very_good_analysis. However, there are certain practices we’ve learned outside of these two places that will make code more maintainable.

Among other things, the release of Dart 3.0 introduced record types, a way to store two different but related pieces of data without creating a separate data class. When using record types, be sure to choose expressive names for positional values.

Future<(String, String)> getUserNameAndEmail() async => _someApiFetchMethod();
final userData = await getUserNameAndEmail();
// a bunch of other code...
if (userData.$1.isValid) {
// do stuff
}

The above example will compile, but it is not immediately obvious what value userData.$1 refers to here. The name of the function gives the reader the impression that the second value in the record is the email, but it is not clear. Particularly in a large codebase, where there could be more processing in between the call to getUserNameAndEmail() and the check on userData.$1, reviewers will not be able to tell immediately what is going on here.

Future<(String, String)> getUserNameAndEmail() async => _someApiFetchMethod();
final (username, email) = await getUserNameAndEmail();
// a bunch of other code...
if (email.isValid) {
// do stuff
}

Now, we are expressly naming the values that we are getting from our record type. Any reviewer or future maintainer of this code will know what value is being validated.

We prefer creating widgets over creating methods that return Widget.

class ParentWidget extends StatelessWidget {
const ParentWidget({super.key});
@override
Widget build(BuildContext context) {
return _buildChildWidget(context);
}
Widget _buildChildWidget(BuildContext context) {
return const Text('Hello World!');
}
}

We prefer this for a few reasons:

  1. It avoids coding errors caused by passing around the wrong BuildContext. Flutter manages the BuildContext via the widget tree, which is more reliable.

  2. The widgets are added to the widget tree, which allows for more potentially efficient rendering and enables inspecting them in the debug tools.

  3. Widgets are easier to test as they can be tested in isolation. They don’t required building the ParentWidget to test the ChildWidget.

For more details, check out the following video: