Photo by Markus Spiske from Pexels (edited)

Best practices — Basic tips on writing good flutter code

Simon Auer
8 min readNov 20, 2020

Flutter is an awesome framework and can save a lot of time in the development of cross-device/system apps.

Being based on the Dart programming language it has a very flexible typed programming language at its core which already “forces” some best practices on you.

There are still a couple of small things, that can make your coding style, performance, and your overall code better though.

TLDR:

Install the pedantic code analyzer package and write clean code:
https://pub.dev/packages/pedantic

Give me details

Disclaimer:

We are a tech loving software agency, who have been working with frontend frameworks like React, Angular, Vue and Flutter on a regular basis, so we have had quite a bit of experience with it and found these best practices to work quite well for our projects.

This is basically a little polished up version of our internal best practices, so other people can profit from them as well. (we will try to update it from time to time to bigger changes, when they come up)

They are however opiniated to the way we like them.

The following list is not meant and can never be a complete list of what you should and shouldn’t do, but should be considered as a good starting point for creating your own best practices.

We have tried to summarize the most important recommendations with reasons, why we consider them important.

So you can always decide whether to follow or just ignore it.

So without further ado…

Follow the latest dart/flutter conventions

Yeah, I know … this is a no-brainer, but often overlooked anyway.

But sometimes we get too excited with flutter for your first real-world project and forget to take the time to read the code style guides.

This is a big mistake because these 10–20 minutes spent reading, can save you hours and a lot of trouble later.

The reason, why we recommend following these code style conventions is not only because we are sure, that the guys from Dart/Flutter took quite some time to think and evaluate them, but also because we want to be able to include and contribute to other peoples flutter code and also write our own packages, that are easy for other people to contribute to.

If we all follow the same structure, no one has to adjust to different code styles for every package.

Also, it will make life with a lot of awesome helper tools easier.

Here is the dart coding style guide:

https://dart.dev/guides/language/effective-dart/style

Just a short heads up, most of the stuff following now, repeats the most important points of these two links with just a few additional (opinionated) tips.

Basic Practices

Follow Naming Conventions

Make sure to always follow the naming conventions proposed by dart/flutter.

snake_case_with_underscores: file and folder names

Example folder structure:

lib
-- my_widget_folder
---- my_widget.dart
---- yet_another_widget.dart
-- my_helper_classes_folder
---- the_helper.dar

UpperCamelCase: classes, type definitions, enums, extensions

# classes
class MyAwesomeWidget { ... }
class HelperClass { ... }
# Type definitions
typedef Predicate<T> = bool Function(T value);
# Enums
enum Status { open, pending, done }
# Extension
extension NumberParsing<T> on String<T> { ...

lowerCamelCase: variables, constants, class members, parameters

# variables
String myString = 'What a wonderful day';
int myNumber = 3;
# constants
const String myString = 'I stay this way';
# parameters
void doMagic(firstParameter = 3) {
...

“_” prefix: private variable names and methods

Private variables and methods in dart are prefixed with an underscore.

(don’t use it for anything else to avoid confusion)

# just a private method name with "_"
void _privateMethod() {
...
}
# a private class property
class Song {
final Boolean _isNice = true;
}

Use dartfmt

Everyone has different preferences for writing code and everyone is convinced that their code style is the most readable. I personally love a lot of whitespace, but some people like it more compact.

In the end, it’s easy to get used to other code styles though and once you get used to them, you think everything else is unreadable.

For that reason: let’s get used to a code style already widely used by dart code in other packages.

The awesome tool dartfmt (an opinionated formatter for dart code) makes sure you don’t have to think about every space, bracket position, … and still follow the general code style guidelines, that everybody else does.

The dartfmt tool is basically the standard now for formatting dart code and is supported by major IDEs.

  • In vscode install the Flutter extension
  • In WebStorm/IntelliJ install the Dart package

We prefer to set it up, to automatically format, whenever we save a file.

To enable that in VSCode just change the editor.formatOnSave setting on true .

See the flutter docs for more details: Flutter Docs — Code Formatting

Extra Tip:

Use trailing commas (optional commas, at the end of lines) to make sure dartfmt formats your code nicer by putting the end bracket in its own line.

(See also Code Formatting link above)

Use relative paths for files in lib/nearby directory

While the import 'package:myproject/my_widget.dart'; form of requiring files is quite nice, you should still try to avoid it for "local" files in your lib folder or when the file is nearby.

Don’t

import 'package:myproject/my_widget.dart';

Do:

import '../my_widget.dart';
  1. If you rename your project/package, you don’t have to worry about it
  2. it visually differentiates between imported files from packages (you mostly will not change) and local files (you work with), so you have a better overview, what is part of your current project code and what is not.

Always specify types

Specific types don’t just make the development experience nicer by providing better auto-completion in your IDE and are vital in bigger projects, but will save you also from a lot of problems (logic AND misinterpretation by dart itself) later on.

ALWAYS set the type while you write and define the variable and don’t push it to a later moment.

If you use the variable and then define the type a day/a week/a decade later, you will not know exactly what edge cases you had in mind.

No matter whether it is just a string, number, object-class, or an enum. Define it before you even finish the initialization command and then use it.

Don’t:

const primaryColor = Color(0xff00ffff); # notice the missing type definition (between "const" and "primaryColor" as opposed to the example below
var iWillChange = 'rectangle';
final theSong = Song();

Do:

const Color primaryColor = Color(0xff00ffff);
var String iWillChange = 'rectangle';
final Song theSong = Song();

Check instead of “force cast” types

Dart is pretty smart about inferring types, but sometimes the system is wrong and we know better.

This can be done in one of two ways:

Don’t — Cast the type:

// cast the variable as a specific type
(melody as Song).title = 'Santa Baby';

Do — Check the type

// check if the type is a certain type
if (melody is Song)
song.title = 'Stille Nacht';

Rather use the second one. (Checking the type)

The reason for that is that most of the time we might be right with the first one and we know better.

But what if for whatever reason melody is not of type Song? Then we would get an exception in the first one, which is bad.

The second one however would just skip the part that could break the application, and not throw an Exception.

Use const for static Widgets

Flutter is pretty fast. It implements some smart logic to see whether something has changed and whether it should rerender. The bigger the application gets, however, the more of these checks for changed data have to be made internally.

Some of our widgets will never really change however and if that is the case, we can just use the const keyword, to signal to Flutter, that no check or rerender will ever be necessary on this Widget after the first one.

Don’t:

Class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return View(
# the arrow back will always look the same
# right now flutter would rerender it everytime to make sure nothing changed
child: CustomIcon('arrow_back');
);
}
}

Do:

Class CustomIcon extends StatelessWidget {
const CustomIcon(@required this.name); # The included icon has to be defined as a "const" as well to be able to use it with const
final String name;
...
}
Class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return View(
# we know, that once I tell this widget to show the "arrow_back" icon
# there will be no need to rerender this, because the arrow_back icon will always be the same
child: const CustomIcon('arrow_back');
);
}

Use flutters null safety features

This topic has long been one of flutters major problems, but starting with build 1.24.0-10.2-pre now even has sound support of null safety!

This means that by default a type definition always expects a value.

If your variable could be nullable , you have to append the question mark to the type definition.

# this variable can only have an object as value and not be null
Painter theBestPainter = Painter('Bob Ross');
# this variable can either be an object or null
Painter? theBestPainter = null;

This makes our life and the life of our analyzer a lot easier because it will know when something is not expected to be null and when it is.

What if something is expected to be null, but we don’t think about it?

Well, that actually happens from time to time and can cause major problems like exceptions or even crashes.

# we init a painter, that is null
Painter? theBestPainter = null;
# then we try to call a method, that we expect from this painter, but if it's null, there is no method -> Exception!!
theBestPainter.paintSomething();

To avoid this, always make sure to use either if conditions or conditional chaining:

If conditions:

You probably have done this a thousand times. Just check whether it exists and only do stuff if it does.

if (theBestPainter == null) {
# This will never throw an exception, because it is only done, if theBestPainer is defined
theBestPainter.paintSomething();
}

Conditional chaining:

An even more elegant solution in this case (not always) would be to use conditional chaining.

Conditional chaining does basically the same thing as above, by just saying, if something is null, it will stop the next call.

# notice the "?" after "theBestPainter". This tells dart to check, whether "theBestPainter" has a value not equal to null, before going on
theBestPainter?.paintSomething();

This even works with nested chains

# it doesn't matter how deep we go. It will work.
theBestPainter?.youngestDaughter?.oldestSon?.paintSomething();

Bonus tip!! Most important rule: Use strict analysis rules

All of the above is pretty important, but it’s just too much to remember.

This is where the amazing flutter analyze command comes in.

It basically analyzes your code for faulty code styles, code smells, and problems.

You probably already have seen its magic because if you install the Dart and Flutter packages for VsCode and IntelliJ it tells you if you did something wrong.

So it works (almost) out of the box, BUT the defaults it uses are (at least for our taste) not strict enough to enforce (some of) the things above.

That’s why I would recommend to adjust your analysis options and use the amazingly strict defaults by the pedantic package.

It’s like a very strict ESLint (for all the javascript/typescript devs out there) — I love it!

Just add the following file (or add to it, if it already exists) in your root:

analysis_options.yaml

# This includes the pedantic rules for linting. You could also add your custom rules after that and overwrite the ones you don't like
include: package:pedantic/analysis_options.yaml

Now you will see a lot of recommendations on what’s wrong with your code AND even though this is annoying at the beginning, it will make your code and your coding skills a lot better!

See more https://pub.dev/packages/pedantic

Have fun writing the code

Ok, ok … the most important best practice is actually still missing:

Have fun writing your code! Flutter is an awesome language and it is a lot of fun to create amazing apps, desktop applications, and PWAs with it, but you need to spend some time playing with it.

Have fun and please add your feedback and missing parts

--

--

Simon Auer

I develop software using modern technologies like Laravel, React, React Native and Flutter. Follow me also on https://twitter.com/SimonEritsch