Effective Dart: Usage
You can use these guidelines every day in the bodies of your Dart code. Users of your library may not be able to tell that you’ve internalized the ideas here, but maintainers of it sure will.
Libraries
These guidelines help you compose your program out of multiple files in a consistent, maintainable way. To keep these guidelines brief, they use “import” to cover import
and export
directives. The guidelines apply equally to both.
part of
directives.
DO use strings in Many Dart developers avoid using part
entirely. They find it easier to reason about their code when each library is a single file. If you do choose to usepart
to split part of a library out into another file, Dart requires the other file to in turn indicate which library it’s a part of. For legacy reasons, Dart allows this part of
directive to use the name of the library it’s a part of. That makes it harder for tools to physically find the main library file, and can make it ambiguous which library the part is actually part of.
The preferred, modern syntax is to use a URI string that points directly to the library file, just like you use in other directives. If you have some library,my_library.dart
, that contains:
1 2 3 |
<span class="kwd">library</span><span class="pln"> my_library</span><span class="pun">;</span> <span class="kwd">part</span> <span class="str">"some/other/file.dart"</span><span class="pun">;</span> |
Then the part file should look like:
1 |
<span class="kwd">part of</span> <span class="str">"../../my_library.dart"</span><span class="pun">;</span> |
And not:
1 |
<span class="kwd">part of</span><span class="pln"> my_library</span><span class="pun">;</span> |
src
directory of another package.
DON’T import libraries that are inside the The src
directory under lib
is specified to contain libraries private to the package’s own implementation. The way package maintainers version their package takes this convention into account. They are free to make sweeping changes to code under src
without it being a breaking change to the package.
That means that if you import some other package’s private library, a minor, theoretically non-breaking point release of that package could break your code.
lib
directory.
PREFER relative paths when importing libraries within your own package’s Linter rule: avoid_relative_lib_imports
When referencing a library inside your package’s lib
directory from another library in that same package, either a relative URI or an explicit package:
will work.
For example, say your directory structure looks like:
1 2 3 4 5 6 |
my_package └─ lib ├─ src │ └─ utils.dart └─ api.dart |
If api.dart
wants to import utils.dart
, it should do so using:
1 |
<span class="kwd">import</span> <span class="str">'src/utils.dart'</span><span class="pun">;</span> |
And not:
1 |
<span class="kwd">import</span> <span class="str">'package:my_package/src/utils.dart'</span><span class="pun">;</span> |
There is no profound reason to prefer the former—it’s just shorter, and we want to be consistent.
The “within your own package’s lib
directory” part is important. Libraries inside lib
can import other libraries inside lib
(or in subdirectories of it). Libraries outside of lib
can use relative imports to reach other libraries outside of lib
. For example, you may have a test utility library under test
that other libraries in test
import.
But you can’t “cross the streams”. A library outside of lib
should never use a relative import to reach a library under lib
, or vice versa. Doing so will break Dart’s ability to correctly tell if two library URIs refer to the same library. Follow these two rules:
- An import path should never contain
/lib/
. - A library under
lib
should never use../
to escape thelib
directory.
Booleans
??
to convert null
to a boolean value.
DO use This rule applies when an expression can evaluate true
, false
, or null
, and you need to pass the result to something that doesn’t accept null
. A common case is the result of a null-aware method call being used as a condition:
1 2 3 |
<span class="kwd">if</span> <span class="pun">(</span><span class="pln">optionalThing</span><span class="pun">?.</span><span class="pln">isEnabled</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"Have enabled thing."</span><span class="pun">);</span> <span class="pun">}</span> |
This code throws an exception if optionalThing
is null
. To fix this, you need to “convert” the null
value to either true
or false
. Although you could do this using ==
, we recommend using ??
:
1 2 3 4 5 |
<span class="com">// If you want null to be false:</span><span class="pln"> optionalThing</span><span class="pun">?.</span><span class="pln">isEnabled </span><span class="pun">??</span> <span class="kwd">false</span><span class="pun">;</span> <span class="com">// If you want null to be true:</span><span class="pln"> optionalThing</span><span class="pun">?.</span><span class="pln">isEnabled </span><span class="pun">??</span> <span class="kwd">true</span><span class="pun">;</span> |
1 2 3 4 5 |
<span class="com">// If you want null to be false:</span><span class="pln"> optionalThing</span><span class="pun">?.</span><span class="pln">isEnabled </span><span class="pun">==</span> <span class="kwd">true</span><span class="pun">;</span> <span class="com">// If you want null to be true:</span><span class="pln"> optionalThing</span><span class="pun">?.</span><span class="pln">isEnabled </span><span class="pun">!=</span> <span class="kwd">false</span><span class="pun">;</span> |
Both operations produce the same result and do the right thing, but ??
is preferred for three main reasons:
- The
??
operator clearly signals that the code has something to do with anull
value. - The
== true
looks like a common new programmer mistake where the equality operator is redundant and can be removed. That’s true when the boolean expression on the left will not producenull
, but not when it can. - The
?? false
and?? true
clearly show what value will be used when the expression isnull
. With== true
, you have to think through the boolean logic to realize that means that anull
gets converted to false.
Strings
Here are some best practices to keep in mind when composing strings in Dart.
DO use adjacent strings to concatenate string literals.
Linter rule: prefer_adjacent_string_concatenation
If you have two string literals—not values, but the actual quoted literal form—you do not need to use +
to concatenate them. Just like in C and C++, simply placing them next to each other does it. This is a good way to make a single long string that doesn’t fit on one line.
1 2 3 |
<span class="pln">raiseAlarm</span><span class="pun">(</span> <span class="str">'ERROR: Parts of the spaceship are on fire. Other '</span> <span class="str">'parts are overrun by martians. Unclear which are which.'</span><span class="pun">);</span> |
1 2 |
<span class="pln">raiseAlarm</span><span class="pun">(</span><span class="str">'ERROR: Parts of the spaceship are on fire. Other '</span> <span class="pun">+</span> <span class="str">'parts are overrun by martians. Unclear which are which.'</span><span class="pun">);</span> |
PREFER using interpolation to compose strings and values.
Linter rule: prefer_interpolation_to_compose_strings
If you’re coming from other languages, you’re used to using long chains of +
to build a string out of literals and other values. That does work in Dart, but it’s almost always cleaner and shorter to use interpolation:
1 |
<span class="str">'Hello, $name! You are ${year - birth} years old.'</span><span class="pun">;</span> |
1 |
<span class="str">'Hello, '</span> <span class="pun">+</span><span class="pln"> name </span><span class="pun">+</span> <span class="str">'! You are '</span> <span class="pun">+</span> <span class="pun">(</span><span class="pln">year </span><span class="pun">-</span><span class="pln"> birth</span><span class="pun">).</span><span class="pln">toString</span><span class="pun">()</span> <span class="pun">+</span> <span class="str">' y...'</span><span class="pun">;</span> |
AVOID using curly braces in interpolation when not needed.
Linter rule: unnecessary_brace_in_string_interps
If you’re interpolating a simple identifier not immediately followed by more alphanumeric text, the {}
should be omitted.
1 2 3 |
<span class="str">'Hi, $name!'</span> <span class="str">"Wear your wildest $decade's outfit."</span> <span class="str">'Wear your wildest ${decade}s outfit.'</span> |
1 2 |
<span class="str">'Hi, ${name}!'</span> <span class="str">"Wear your wildest ${decade}'s outfit."</span> |
Collections
Out of the box, Dart supports four collection types: lists, maps, queues, and sets. The following best practices apply to collections.
DO use collection literals when possible.
Linter rule: prefer_collection_literals
There are two ways to make an empty growable list: []
and List()
. Likewise, there are three ways to make an empty linked hash map: {}
, Map()
, and LinkedHashMap()
.
If you want to create a non-growable list, or some other custom collection type then, by all means, use a constructor. Otherwise, use the nice literal syntax. The core library exposes those constructors to ease adoption, but idiomatic Dart code does not use them.
1 2 |
<span class="kwd">var</span><span class="pln"> points </span><span class="pun">=</span> <span class="pun">[];</span> <span class="kwd">var</span><span class="pln"> addresses </span><span class="pun">=</span> <span class="pun">{};</span> |
1 2 |
<span class="kwd">var</span><span class="pln"> points </span><span class="pun">=</span> <span class="typ">List</span><span class="pun">();</span> <span class="kwd">var</span><span class="pln"> addresses </span><span class="pun">=</span> <span class="typ">Map</span><span class="pun">();</span> |
You can even provide a type argument for them if that matters.
1 2 |
<span class="kwd">var</span><span class="pln"> points </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">Point</span><span class="pun">>[];</span> <span class="kwd">var</span><span class="pln"> addresses </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">String</span><span class="pun">,</span> <span class="typ">Address</span><span class="pun">>{};</span> |
1 2 |
<span class="kwd">var</span><span class="pln"> points </span><span class="pun">=</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">Point</span><span class="pun">>();</span> <span class="kwd">var</span><span class="pln"> addresses </span><span class="pun">=</span> <span class="typ">Map</span><span class="pun"><</span><span class="typ">String</span><span class="pun">,</span> <span class="typ">Address</span><span class="pun">>();</span> |
Note that this doesn’t apply to the named constructors for those classes. List.from()
, Map.fromIterable()
, and friends all have their uses. Likewise, if you’re passing a size to List()
to create a non-growable one, then it makes sense to use that.
.length
to see if a collection is empty.
DON’T use The Iterable contract does not require that a collection know its length or be able to provide it in constant time. Calling .length
just to see if the collection contains anything can be painfully slow.
Instead, there are faster and more readable getters: .isEmpty
and .isNotEmpty
. Use the one that doesn’t require you to negate the result.
1 2 |
<span class="kwd">if</span> <span class="pun">(</span><span class="pln">lunchBox</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">)</span> <span class="kwd">return</span> <span class="str">'so hungry...'</span><span class="pun">;</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">words</span><span class="pun">.</span><span class="pln">isNotEmpty</span><span class="pun">)</span> <span class="kwd">return</span><span class="pln"> words</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">' '</span><span class="pun">);</span> |
1 2 |
<span class="kwd">if</span> <span class="pun">(</span><span class="pln">lunchBox</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span> <span class="lit">0</span><span class="pun">)</span> <span class="kwd">return</span> <span class="str">'so hungry...'</span><span class="pun">;</span> <span class="kwd">if</span> <span class="pun">(!</span><span class="pln">words</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">)</span> <span class="kwd">return</span><span class="pln"> words</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">' '</span><span class="pun">);</span> |
CONSIDER using higher-order methods to transform a sequence.
If you have a collection and want to produce a new modified collection from it, it’s often shorter and more declarative to use .map()
, .where()
, and the other handy methods on Iterable
.
Using those instead of an imperative for
loop makes it clear that your intent is to produce a new sequence and not to produce side effects.
1 2 3 |
<span class="kwd">var</span><span class="pln"> aquaticNames </span><span class="pun">=</span><span class="pln"> animals </span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">animal</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> animal</span><span class="pun">.</span><span class="pln">isAquatic</span><span class="pun">)</span> <span class="pun">.</span><span class="pln">map</span><span class="pun">((</span><span class="pln">animal</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> animal</span><span class="pun">.</span><span class="pln">name</span><span class="pun">);</span> |
At the same time, this can be taken too far. If you are chaining or nesting many higher-order methods, it may be clearer to write a chunk of imperative code.
Iterable.forEach()
with a function literal.
AVOID using Linter rule: avoid_function_literals_in_foreach_calls
forEach()
functions are widely used in JavaScript because the built in for-in
loop doesn’t do what you usually want. In Dart, if you want to iterate over a sequence, the idiomatic way to do that is using a loop.
1 2 3 |
<span class="kwd">for</span> <span class="pun">(</span><span class="kwd">var</span><span class="pln"> person </span><span class="kwd">in</span><span class="pln"> people</span><span class="pun">)</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">}</span> |
1 2 3 |
<span class="pln">people</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">((</span><span class="pln">person</span><span class="pun">)</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">});</span> |
Note that this guideline specifically says “function literal”. If you want to invoke some already existing function on each element, forEach()
is fine.
1 |
<span class="pln">people</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">print</span><span class="pun">);</span> |
Also note that it’s always OK to use Map.forEach()
. Maps aren’t iterable, so this guideline doesn’t apply.
List.from()
unless you intend to change the type of the result.
DON’T use Given an Iterable, there are two obvious ways to produce a new List that contains the same elements:
1 2 |
<span class="kwd">var</span><span class="pln"> copy1 </span><span class="pun">=</span><span class="pln"> iterable</span><span class="pun">.</span><span class="pln">toList</span><span class="pun">();</span> <span class="kwd">var</span><span class="pln"> copy2 </span><span class="pun">=</span> <span class="typ">List</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">iterable</span><span class="pun">);</span> |
The obvious difference is that the first one is shorter. The important difference is that the first one preserves the type argument of the original object:
1 2 3 4 5 |
<span class="com">// Creates a List<int>:</span> <span class="kwd">var</span><span class="pln"> iterable </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">,</span> <span class="lit">3</span><span class="pun">];</span> <span class="com">// Prints "List<int>":</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">iterable</span><span class="pun">.</span><span class="pln">toList</span><span class="pun">().</span><span class="pln">runtimeType</span><span class="pun">);</span> |
1 2 3 4 5 |
<span class="com">// Creates a List<int>:</span> <span class="kwd">var</span><span class="pln"> iterable </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">,</span> <span class="lit">3</span><span class="pun">];</span> <span class="com">// Prints "List<dynamic>":</span><span class="pln"> print</span><span class="pun">(</span><span class="typ">List</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">iterable</span><span class="pun">).</span><span class="pln">runtimeType</span><span class="pun">);</span> |
If you want to change the type, then calling List.from()
is useful:
1 2 3 |
<span class="kwd">var</span><span class="pln"> numbers </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2.3</span><span class="pun">,</span> <span class="lit">4</span><span class="pun">];</span> <span class="com">// List<num>.</span><span class="pln"> numbers</span><span class="pun">.</span><span class="pln">removeAt</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span> <span class="com">// Now it only contains integers.</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">);</span> |
But if your goal is just to copy the iterable and preserve its original type, or you don’t care about the type, then use toList()
.
whereType()
to filter a collection by type.
DO use Linter rule: prefer_iterable_whereType
Let’s say you have a list containing a mixture of objects, and you want to get just the integers out of it. You could use where()
like this:
1 2 |
<span class="kwd">var</span><span class="pln"> objects </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="str">"a"</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">,</span> <span class="str">"b"</span><span class="pun">,</span> <span class="lit">3</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span><span class="pln"> objects</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> e </span><span class="kwd">is</span> <span class="typ">int</span><span class="pun">);</span> |
This is verbose, but, worse, it returns an iterable whose type probably isn’t what you want. In the example here, it returns an Iterable<Object>
even though you likely want an Iterable<int>
since that’s the type you’re filtering it to.
Sometimes you see code that “corrects” the above error by adding cast()
:
1 2 |
<span class="kwd">var</span><span class="pln"> objects </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="str">"a"</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">,</span> <span class="str">"b"</span><span class="pun">,</span> <span class="lit">3</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span><span class="pln"> objects</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> e </span><span class="kwd">is</span> <span class="typ">int</span><span class="pun">).</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>();</span> |
That’s verbose and causes two wrappers to be created, with two layers of indirection and redundant runtime checking. Fortunately, the core library has the whereType()
method for this exact use case:
1 2 |
<span class="kwd">var</span><span class="pln"> objects </span><span class="pun">=</span> <span class="pun">[</span><span class="lit">1</span><span class="pun">,</span> <span class="str">"a"</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">,</span> <span class="str">"b"</span><span class="pun">,</span> <span class="lit">3</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span><span class="pln"> objects</span><span class="pun">.</span><span class="pln">whereType</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>();</span> |
Using whereType()
is concise, produces an Iterable of the desired type, and has no unnecessary levels of wrapping.
cast()
when a nearby operation will do.
DON’T use Often when you’re dealing with an iterable or stream, you perform several transformations on it. At the end, you want to produce an object with a certain type argument. Instead of tacking on a call to cast()
, see if one of the existing transformations can change the type.
If you’re already calling toList()
, replace that with a call to List<T>.from()
where T
is the type of resulting list you want.
1 2 |
<span class="kwd">var</span><span class="pln"> stuff </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">dynamic</span><span class="pun">>[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">stuff</span><span class="pun">);</span> |
1 2 |
<span class="kwd">var</span><span class="pln"> stuff </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">dynamic</span><span class="pun">>[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span><span class="pln"> stuff</span><span class="pun">.</span><span class="pln">toList</span><span class="pun">().</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>();</span> |
If you are calling map()
, give it an explicit type argument so that it produces an iterable of the desired type. Type inference often picks the correct type for you based on the function you pass to map()
, but sometimes you need to be explicit.
1 2 |
<span class="kwd">var</span><span class="pln"> stuff </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">dynamic</span><span class="pun">>[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> reciprocals </span><span class="pun">=</span><span class="pln"> stuff</span><span class="pun">.</span><span class="pln">map</span><span class="pun"><</span><span class="typ">double</span><span class="pun">>((</span><span class="pln">n</span><span class="pun">)</span> <span class="pun">=></span> <span class="lit">1</span> <span class="pun">/</span><span class="pln"> n</span><span class="pun">);</span> |
1 2 |
<span class="kwd">var</span><span class="pln"> stuff </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">dynamic</span><span class="pun">>[</span><span class="lit">1</span><span class="pun">,</span> <span class="lit">2</span><span class="pun">];</span> <span class="kwd">var</span><span class="pln"> reciprocals </span><span class="pun">=</span><span class="pln"> stuff</span><span class="pun">.</span><span class="pln">map</span><span class="pun">((</span><span class="pln">n</span><span class="pun">)</span> <span class="pun">=></span> <span class="lit">1</span> <span class="pun">/</span><span class="pln"> n</span><span class="pun">).</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">double</span><span class="pun">>();</span> |
cast()
.
AVOID using This is the softer generalization of the previous rule. Sometimes there is no nearby operation you can use to fix the type of some object. Even then, when possible avoid using cast()
to “change” a collection’s type.
Prefer any of these options instead:
- Create it with the right type. Change the code where the collection is first created so that it has the right type.
- Cast the elements on access. If you immediately iterate over the collection, cast each element inside the iteration.
- Eagerly cast using
List.from()
. If you’ll eventually access most of the elements in the collection, and you don’t need the object to be backed by the original live object, convert it usingList.from()
.Thecast()
method returns a lazy collection that checks the element type on every operation. If you perform only a few operations on only a few elements, that laziness can be good. But in many cases, the overhead of lazy validation and of wrapping outweighs the benefits.
Here is an example of creating it with the right type:
1 2 3 4 5 |
<span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">></span><span class="pln"> singletonList</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> value</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> list </span><span class="pun">=</span> <span class="pun"><</span><span class="typ">int</span><span class="pun">>[];</span><span class="pln"> list</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> list</span><span class="pun">;</span> <span class="pun">}</span> |
1 2 3 4 5 |
<span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">></span><span class="pln"> singletonList</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> value</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> list </span><span class="pun">=</span> <span class="pun">[];</span> <span class="com">// List<dynamic>.</span><span class="pln"> list</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> list</span><span class="pun">.</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>();</span> <span class="pun">}</span> |
Here is casting each element on access:
1 2 3 4 5 6 |
<span class="typ">void</span><span class="pln"> printEvens</span><span class="pun">(</span><span class="typ">List</span><span class="pun"><</span><span class="typ">Object</span><span class="pun">></span><span class="pln"> objects</span><span class="pun">)</span> <span class="pun">{</span> <span class="com">// We happen to know the list only contains ints.</span> <span class="kwd">for</span> <span class="pun">(</span><span class="kwd">var</span><span class="pln"> n </span><span class="kwd">in</span><span class="pln"> objects</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="highlight"><span class="pun">(</span><span class="pln">n </span><span class="kwd">as</span> <span class="typ">int</span><span class="pun">)</span></span><span class="pun">.</span><span class="pln">isEven</span><span class="pun">)</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 6 |
<span class="typ">void</span><span class="pln"> printEvens</span><span class="pun">(</span><span class="typ">List</span><span class="pun"><</span><span class="typ">Object</span><span class="pun">></span><span class="pln"> objects</span><span class="pun">)</span> <span class="pun">{</span> <span class="com">// We happen to know the list only contains ints.</span> <span class="kwd">for</span> <span class="pun">(</span><span class="kwd">var</span><span class="pln"> n </span><span class="kwd">in</span><span class="pln"> objects</span><span class="pun">.</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>())</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">n</span><span class="pun">.</span><span class="pln">isEven</span><span class="pun">)</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span> <span class="pun">}</span> <span class="pun">}</span> |
Here is casting eagerly using List.from()
:
1 2 3 4 5 6 |
<span class="typ">int</span><span class="pln"> median</span><span class="pun">(</span><span class="typ">List</span><span class="pun"><</span><span class="typ">Object</span><span class="pun">></span><span class="pln"> objects</span><span class="pun">)</span> <span class="pun">{</span> <span class="com">// We happen to know the list only contains ints.</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">objects</span><span class="pun">);</span><span class="pln"> ints</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">();</span> <span class="kwd">return</span><span class="pln"> ints</span><span class="pun">[</span><span class="pln">ints</span><span class="pun">.</span><span class="pln">length </span><span class="pun">~/</span> <span class="lit">2</span><span class="pun">];</span> <span class="pun">}</span> |
1 2 3 4 5 6 |
<span class="typ">int</span><span class="pln"> median</span><span class="pun">(</span><span class="typ">List</span><span class="pun"><</span><span class="typ">Object</span><span class="pun">></span><span class="pln"> objects</span><span class="pun">)</span> <span class="pun">{</span> <span class="com">// We happen to know the list only contains ints.</span> <span class="kwd">var</span><span class="pln"> ints </span><span class="pun">=</span><span class="pln"> objects</span><span class="pun">.</span><span class="pln">cast</span><span class="pun"><</span><span class="typ">int</span><span class="pun">>();</span><span class="pln"> ints</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">();</span> <span class="kwd">return</span><span class="pln"> ints</span><span class="pun">[</span><span class="pln">ints</span><span class="pun">.</span><span class="pln">length </span><span class="pun">~/</span> <span class="lit">2</span><span class="pun">];</span> <span class="pun">}</span> |
These alternatives don’t always work, of course, and sometimes cast()
is the right answer. But consider that method a little risky and undesirable—it can be slow and may fail at runtime if you aren’t careful.
Functions
In Dart, even functions are objects. Here are some best practices involving functions.
DO use a function declaration to bind a function to a name.
Linter rule: prefer_function_declarations_over_variables
Modern languages have realized how useful local nested functions and closures are. It’s common to have a function defined inside another one. In many cases, this function is used as a callback immediately and doesn’t need a name. A function expression is great for that.
But, if you do need to give it a name, use a function declaration statement instead of binding a lambda to a variable.
1 2 3 4 5 |
<span class="typ">void</span><span class="pln"> main</span><span class="pun">()</span> <span class="pun">{</span><span class="pln"> localFunction</span><span class="pun">()</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 |
<span class="typ">void</span><span class="pln"> main</span><span class="pun">()</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> localFunction </span><span class="pun">=</span> <span class="pun">()</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">};</span> <span class="pun">}</span> |
DON’T create a lambda when a tear-off will do.
Linter rule: unnecessary_lambdas
If you refer to a method on an object but omit the parentheses, Dart gives you a “tear-off”—a closure that takes the same parameters as the method and invokes it when you call it.
If you have a function that invokes a method with the same arguments as are passed to it, you don’t need to manually wrap the call in a lambda.
1 |
<span class="pln">names</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">print</span><span class="pun">);</span> |
1 2 3 |
<span class="pln">names</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">((</span><span class="pln">name</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">name</span><span class="pun">);</span> <span class="pun">});</span> |
Parameters
=
to separate a named parameter from its default value.
DO use Linter rule: prefer_equal_for_default_values
For legacy reasons, Dart allows both :
and =
as the default value separator for named parameters. For consistency with optional positional parameters, use =
.
1 |
<span class="typ">void</span><span class="pln"> insert</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> item</span><span class="pun">,</span> <span class="pun">{</span><span class="typ">int</span><span class="pln"> at </span><span class="pun">=</span> <span class="lit">0</span><span class="pun">})</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">}</span> |
1 |
<span class="typ">void</span><span class="pln"> insert</span><span class="pun">(</span><span class="typ">Object</span><span class="pln"> item</span><span class="pun">,</span> <span class="pun">{</span><span class="typ">int</span><span class="pln"> at</span><span class="pun">:</span> <span class="lit">0</span><span class="pun">})</span> <span class="pun">{</span> <span class="pun">...</span> <span class="pun">}</span> |
null
.
DON’T use an explicit default value of Linter rule: avoid_init_to_null
If you make a parameter optional but don’t give it a default value, the language implicitly uses null
as the default, so there’s no need to write it.
1 2 3 |
<span class="typ">void</span><span class="pln"> error</span><span class="pun">([</span><span class="typ">String</span><span class="pln"> message</span><span class="pun">])</span> <span class="pun">{</span><span class="pln"> stderr</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">message </span><span class="pun">??</span> <span class="str">'\n'</span><span class="pun">);</span> <span class="pun">}</span> |
1 2 3 |
<span class="typ">void</span><span class="pln"> error</span><span class="pun">([</span><span class="typ">String</span><span class="pln"> message </span><span class="pun">=</span> <span class="kwd">null</span><span class="pun">])</span> <span class="pun">{</span><span class="pln"> stderr</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">message </span><span class="pun">??</span> <span class="str">'\n'</span><span class="pun">);</span> <span class="pun">}</span> |
Variables
The following best practices describe how to best use variables in Dart.
null
.
DON’T explicitly initialize variables to In Dart, a variable or field that is not explicitly initialized automatically gets initialized to null
. This is reliably specified by the language. There’s no concept of “uninitialized memory” in Dart. Adding = null
is redundant and unneeded.
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="typ">int</span><span class="pln"> _nextId</span><span class="pun">;</span> <span class="kwd">class</span> <span class="typ">LazyId</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> _id</span><span class="pun">;</span> <span class="typ">int</span> <span class="kwd">get</span><span class="pln"> id </span><span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">_nextId </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span><span class="pln"> _nextId </span><span class="pun">=</span> <span class="lit">0</span><span class="pun">;</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">_id </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span><span class="pln"> _id </span><span class="pun">=</span><span class="pln"> _nextId</span><span class="pun">++;</span> <span class="kwd">return</span><span class="pln"> _id</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="typ">int</span><span class="pln"> _nextId </span><span class="pun">=</span> <span class="kwd">null</span><span class="pun">;</span> <span class="kwd">class</span> <span class="typ">LazyId</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> _id </span><span class="pun">=</span> <span class="kwd">null</span><span class="pun">;</span> <span class="typ">int</span> <span class="kwd">get</span><span class="pln"> id </span><span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">_nextId </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span><span class="pln"> _nextId </span><span class="pun">=</span> <span class="lit">0</span><span class="pun">;</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">_id </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span><span class="pln"> _id </span><span class="pun">=</span><span class="pln"> _nextId</span><span class="pun">++;</span> <span class="kwd">return</span><span class="pln"> _id</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
AVOID storing what you can calculate.
When designing a class, you often want to expose multiple views into the same underlying state. Often you see code that calculates all of those views in the constructor and then stores them:
1 2 3 4 5 6 7 8 9 10 |
<span class="kwd">class</span> <span class="typ">Circle</span> <span class="pun">{</span> <span class="typ">num</span><span class="pln"> radius</span><span class="pun">;</span> <span class="typ">num</span><span class="pln"> area</span><span class="pun">;</span> <span class="typ">num</span><span class="pln"> circumference</span><span class="pun">;</span> <span class="typ">Circle</span><span class="pun">(</span><span class="typ">num</span><span class="pln"> radius</span><span class="pun">)</span> <span class="pun">:</span><span class="pln"> radius </span><span class="pun">=</span><span class="pln"> radius</span><span class="pun">,</span><span class="pln"> area </span><span class="pun">=</span><span class="pln"> pi </span><span class="pun">*</span><span class="pln"> radius </span><span class="pun">*</span><span class="pln"> radius</span><span class="pun">,</span><span class="pln"> circumference </span><span class="pun">=</span><span class="pln"> pi </span><span class="pun">*</span> <span class="lit">2.0</span> <span class="pun">*</span><span class="pln"> radius</span><span class="pun">;</span> <span class="pun">}</span> |
This code has two things wrong with it. First, it’s likely wasting memory. The area and circumference, strictly speaking, are caches. They are stored calculations that we could recalculate from other data we already have. They are trading increased memory for reduced CPU usage. Do we know we have a performance problem that merits that trade-off?
Worse, the code is wrong. The problem with caches is invalidation—how do you know when the cache is out of date and needs to be recalculated? Here, we never do, even though radius
is mutable. You can assign a different value and the area
and circumference
will retain their previous, now incorrect values.
To correctly handle cache invalidation, we need to do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<span class="kwd">class</span> <span class="typ">Circle</span> <span class="pun">{</span> <span class="typ">num</span><span class="pln"> _radius</span><span class="pun">;</span> <span class="typ">num</span> <span class="kwd">get</span><span class="pln"> radius </span><span class="pun">=></span><span class="pln"> _radius</span><span class="pun">;</span> <span class="kwd">set</span><span class="pln"> radius</span><span class="pun">(</span><span class="typ">num</span><span class="pln"> value</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> _radius </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span><span class="pln"> _recalculate</span><span class="pun">();</span> <span class="pun">}</span> <span class="typ">num</span><span class="pln"> _area</span><span class="pun">;</span> <span class="typ">num</span> <span class="kwd">get</span><span class="pln"> area </span><span class="pun">=></span><span class="pln"> _area</span><span class="pun">;</span> <span class="typ">num</span><span class="pln"> _circumference</span><span class="pun">;</span> <span class="typ">num</span> <span class="kwd">get</span><span class="pln"> circumference </span><span class="pun">=></span><span class="pln"> _circumference</span><span class="pun">;</span> <span class="typ">Circle</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">_radius</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> _recalculate</span><span class="pun">();</span> <span class="pun">}</span> <span class="typ">void</span><span class="pln"> _recalculate</span><span class="pun">()</span> <span class="pun">{</span><span class="pln"> _area </span><span class="pun">=</span><span class="pln"> pi </span><span class="pun">*</span><span class="pln"> _radius </span><span class="pun">*</span><span class="pln"> _radius</span><span class="pun">;</span><span class="pln"> _circumference </span><span class="pun">=</span><span class="pln"> pi </span><span class="pun">*</span> <span class="lit">2.0</span> <span class="pun">*</span><span class="pln"> _radius</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
That’s an awful lot of code to write, maintain, debug, and read. Instead, your first implementation should be:
1 2 3 4 5 6 7 8 |
<span class="kwd">class</span> <span class="typ">Circle</span> <span class="pun">{</span> <span class="typ">num</span><span class="pln"> radius</span><span class="pun">;</span> <span class="typ">Circle</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">radius</span><span class="pun">);</span> <span class="typ">num</span> <span class="kwd">get</span><span class="pln"> area </span><span class="pun">=></span><span class="pln"> pi </span><span class="pun">*</span><span class="pln"> radius </span><span class="pun">*</span><span class="pln"> radius</span><span class="pun">;</span> <span class="typ">num</span> <span class="kwd">get</span><span class="pln"> circumference </span><span class="pun">=></span><span class="pln"> pi </span><span class="pun">*</span> <span class="lit">2.0</span> <span class="pun">*</span><span class="pln"> radius</span><span class="pun">;</span> <span class="pun">}</span> |
This code is shorter, uses less memory, and is less error-prone. It stores the minimal amount of data needed to represent the circle. There are no fields to get out of sync because there is only a single source of truth.
In some cases, you may need to cache the result of a slow calculation, but only do that after you know you have a performance problem, do it carefully, and leave a comment explaining the optimization.
Members
In Dart, objects have members which can be functions (methods) or data (instance variables). The following best practices apply to an object’s members.
DON’T wrap a field in a getter and setter unnecessarily.
Linter rule: unnecessary_getters_setters
In Java and C#, it’s common to hide all fields behind getters and setters (or properties in C#), even if the implementation just forwards to the field. That way, if you ever need to do more work in those members, you can without needing to touch the callsites. This is because calling a getter method is different than accessing a field in Java, and accessing a property isn’t binary-compatible with accessing a raw field in C#.
Dart doesn’t have this limitation. Fields and getters/setters are completely indistinguishable. You can expose a field in a class and later wrap it in a getter and setter without having to touch any code that uses that field.
1 2 3 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> contents</span><span class="pun">;</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> _contents</span><span class="pun">;</span> <span class="kwd">get</span><span class="pln"> contents </span><span class="pun">=></span><span class="pln"> _contents</span><span class="pun">;</span> <span class="kwd">set</span><span class="pln"> contents</span><span class="pun">(</span><span class="pln">value</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> _contents </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
final
field to make a read-only property.
PREFER using a Linter rule: unnecessary_getters
If you have a field that outside code should be able to see but not assign to, a simple solution that works in many cases is to simply mark it final
.
1 2 3 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">final</span><span class="pln"> contents </span><span class="pun">=</span> <span class="pun">[];</span> <span class="pun">}</span> |
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> _contents</span><span class="pun">;</span> <span class="kwd">get</span><span class="pln"> contents </span><span class="pun">=></span><span class="pln"> _contents</span><span class="pun">;</span> <span class="pun">}</span> |
Of course, if you need to internally assign to the field outside of the constructor, you may need to do the “private field, public getter” pattern, but don’t reach for that until you need to.
=>
for simple members.
CONSIDER using Linter rule: prefer_expression_function_bodies
In addition to using =>
for function expressions, Dart also lets you define members with it. That style is a good fit for simple members that just calculate and return a value.
1 2 3 4 5 6 |
<span class="typ">double</span> <span class="kwd">get</span><span class="pln"> area </span><span class="pun">=></span> <span class="pun">(</span><span class="pln">right </span><span class="pun">-</span><span class="pln"> left</span><span class="pun">)</span> <span class="pun">*</span> <span class="pun">(</span><span class="pln">bottom </span><span class="pun">-</span><span class="pln"> top</span><span class="pun">);</span> <span class="typ">bool</span><span class="pln"> isReady</span><span class="pun">(</span><span class="typ">num</span><span class="pln"> time</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> minTime </span><span class="pun">==</span> <span class="kwd">null</span> <span class="pun">||</span><span class="pln"> minTime </span><span class="pun"><=</span><span class="pln"> time</span><span class="pun">;</span> <span class="typ">String</span><span class="pln"> capitalize</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> name</span><span class="pun">)</span> <span class="pun">=></span> <span class="str">'${name[0].toUpperCase()}${name.substring(1)}'</span><span class="pun">;</span> |
People writing code seem to love =>
, but it’s very easy to abuse it and end up with code that’s hard to read. If your declaration is more than a couple of lines or contains deeply nested expressions—cascades and conditional operators are common offenders—do yourself and everyone who has to read your code a favor and use a block body and some statements.
1 2 3 4 5 6 7 8 |
<span class="typ">Treasure</span><span class="pln"> openChest</span><span class="pun">(</span><span class="typ">Chest</span><span class="pln"> chest</span><span class="pun">,</span> <span class="typ">Point</span><span class="pln"> where</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">_opened</span><span class="pun">.</span><span class="pln">containsKey</span><span class="pun">(</span><span class="pln">chest</span><span class="pun">))</span> <span class="kwd">return</span> <span class="kwd">null</span><span class="pun">;</span> <span class="kwd">var</span><span class="pln"> treasure </span><span class="pun">=</span> <span class="typ">Treasure</span><span class="pun">(</span><span class="pln">where</span><span class="pun">);</span><span class="pln"> treasure</span><span class="pun">.</span><span class="pln">addAll</span><span class="pun">(</span><span class="pln">chest</span><span class="pun">.</span><span class="pln">contents</span><span class="pun">);</span><span class="pln"> _opened</span><span class="pun">[</span><span class="pln">chest</span><span class="pun">]</span> <span class="pun">=</span><span class="pln"> treasure</span><span class="pun">;</span> <span class="kwd">return</span><span class="pln"> treasure</span><span class="pun">;</span> <span class="pun">}</span> |
1 2 3 |
<span class="typ">Treasure</span><span class="pln"> openChest</span><span class="pun">(</span><span class="typ">Chest</span><span class="pln"> chest</span><span class="pun">,</span> <span class="typ">Point</span><span class="pln"> where</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> _opened</span><span class="pun">.</span><span class="pln">containsKey</span><span class="pun">(</span><span class="pln">chest</span><span class="pun">)</span> <span class="pun">?</span> <span class="kwd">null</span> <span class="pun">:</span><span class="pln"> _opened</span><span class="pun">[</span><span class="pln">chest</span><span class="pun">]</span> <span class="pun">=</span> <span class="typ">Treasure</span><span class="pun">(</span><span class="pln">where</span><span class="pun">)</span> <span class="pun">..</span><span class="pln">addAll</span><span class="pun">(</span><span class="pln">chest</span><span class="pun">.</span><span class="pln">contents</span><span class="pun">);</span> |
You can also use =>
on members that don’t return a value. This is idiomatic when a setter is small and has a corresponding getter that uses =>
.
1 2 |
<span class="typ">num</span> <span class="kwd">get</span><span class="pln"> x </span><span class="pun">=></span><span class="pln"> center</span><span class="pun">.</span><span class="pln">x</span><span class="pun">;</span> <span class="kwd">set</span><span class="pln"> x</span><span class="pun">(</span><span class="typ">num</span><span class="pln"> value</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> center </span><span class="pun">=</span> <span class="typ">Point</span><span class="pun">(</span><span class="pln">value</span><span class="pun">,</span><span class="pln"> center</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span> |
this.
except to redirect to a named constructor or to avoid shadowing.
DON’T use Linter rule: unnecessary_this
JavaScript requires an explicit this.
to refer to members on the object whose method is currently being executed, but Dart—like C++, Java, and C#—doesn’t have that limitation.
There are only two times you need to use this.
. One is when a local variable with the same name shadows the member you want to access:
1 2 3 4 5 6 7 8 9 10 11 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> value</span><span class="pun">;</span> <span class="typ">void</span><span class="pln"> clear</span><span class="pun">()</span> <span class="pun">{</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span> <span class="pun">}</span> <span class="typ">void</span><span class="pln"> update</span><span class="pun">(</span><span class="pln">value</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 11 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> value</span><span class="pun">;</span> <span class="typ">void</span><span class="pln"> clear</span><span class="pun">()</span> <span class="pun">{</span><span class="pln"> update</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span> <span class="pun">}</span> <span class="typ">void</span><span class="pln"> update</span><span class="pun">(</span><span class="pln">value</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
The other time to use this.
is when redirecting to a named constructor:
1 2 3 4 5 6 7 8 9 10 |
<span class="kwd">class</span> <span class="typ">ShadeOfGray</span> <span class="pun">{</span> <span class="kwd">final</span> <span class="typ">int</span><span class="pln"> brightness</span><span class="pun">;</span> <span class="typ">ShadeOfGray</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> val</span><span class="pun">)</span> <span class="pun">:</span><span class="pln"> brightness </span><span class="pun">=</span><span class="pln"> val</span><span class="pun">;</span> <span class="typ">ShadeOfGray</span><span class="pun">.</span><span class="pln">black</span><span class="pun">()</span> <span class="pun">:</span> <span class="kwd">this</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span> <span class="com">// This won't parse or compile!</span> <span class="com">// ShadeOfGray.alsoBlack() : black();</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 |
<span class="kwd">class</span> <span class="typ">ShadeOfGray</span> <span class="pun">{</span> <span class="kwd">final</span> <span class="typ">int</span><span class="pln"> brightness</span><span class="pun">;</span> <span class="typ">ShadeOfGray</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> val</span><span class="pun">)</span> <span class="pun">:</span><span class="pln"> brightness </span><span class="pun">=</span><span class="pln"> val</span><span class="pun">;</span> <span class="typ">ShadeOfGray</span><span class="pun">.</span><span class="pln">black</span><span class="pun">()</span> <span class="pun">:</span> <span class="kwd">this</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span> <span class="com">// But now it will!</span> <span class="typ">ShadeOfGray</span><span class="pun">.</span><span class="pln">alsoBlack</span><span class="pun">()</span> <span class="pun">:</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">black</span><span class="pun">();</span> <span class="pun">}</span> |
Note that constructor parameters never shadow fields in constructor initialization lists:
1 2 3 4 5 6 7 |
<span class="kwd">class</span> <span class="typ">Box</span> <span class="kwd">extends</span> <span class="typ">BaseBox</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> value</span><span class="pun">;</span> <span class="typ">Box</span><span class="pun">(</span><span class="pln">value</span><span class="pun">)</span> <span class="pun">:</span><span class="pln"> value </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">,</span> <span class="kwd">super</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span> <span class="pun">}</span> |
This looks surprising, but works like you want. Fortunately, code like this is relatively rare thanks to initializing formals.
DO initialize fields at their declaration when possible.
If a field doesn’t depend on any constructor parameters, it can and should be initialized at its declaration. It takes less code and makes sure you won’t forget to initialize it if the class has multiple constructors.
1 2 3 4 5 6 7 |
<span class="kwd">class</span> <span class="typ">Folder</span> <span class="pun">{</span> <span class="kwd">final</span> <span class="typ">String</span><span class="pln"> name</span><span class="pun">;</span> <span class="kwd">final</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">Document</span><span class="pun">></span><span class="pln"> contents</span><span class="pun">;</span> <span class="typ">Folder</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name</span><span class="pun">)</span> <span class="pun">:</span><span class="pln"> contents </span><span class="pun">=</span> <span class="pun">[];</span> <span class="typ">Folder</span><span class="pun">.</span><span class="pln">temp</span><span class="pun">()</span> <span class="pun">:</span><span class="pln"> name </span><span class="pun">=</span> <span class="str">'temporary'</span><span class="pun">;</span> <span class="com">// Oops! Forgot contents.</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 |
<span class="kwd">class</span> <span class="typ">Folder</span> <span class="pun">{</span> <span class="kwd">final</span> <span class="typ">String</span><span class="pln"> name</span><span class="pun">;</span> <span class="kwd">final</span> <span class="typ">List</span><span class="pun"><</span><span class="typ">Document</span><span class="pun">></span><span class="pln"> contents </span><span class="pun">=</span> <span class="pun">[];</span> <span class="typ">Folder</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name</span><span class="pun">);</span> <span class="typ">Folder</span><span class="pun">.</span><span class="pln">temp</span><span class="pun">()</span> <span class="pun">:</span><span class="pln"> name </span><span class="pun">=</span> <span class="str">'temporary'</span><span class="pun">;</span> <span class="pun">}</span> |
Of course, if a field depends on constructor parameters, or is initialized differently by different constructors, then this guideline does not apply.
Constructors
The following best practices apply to declaring constructors for a class.
DO use initializing formals when possible.
Linter rule: prefer_initializing_formals
Many fields are initialized directly from a constructor parameter, like:
1 2 3 4 5 6 7 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">num</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="typ">num</span><span class="pln"> x</span><span class="pun">,</span> <span class="typ">num</span><span class="pln"> y</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> x</span><span class="pun">;</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> y</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
We’ve got to type x
four times here define a field. We can do better:
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">num</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span> <span class="pun">}</span> |
This this.
syntax before a constructor parameter is called an “initializing formal”. You can’t always take advantage of it. Sometimes you want to have a named parameter whose name doesn’t match the name of the field you are initializing. But when you can use initializing formals, you should.
DON’T type annotate initializing formals.
Linter rule: type_init_formals
If a constructor parameter is using this.
to initialize a field, then the type of the parameter is understood to be the same type as the field.
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span> <span class="pun">}</span> |
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="typ">int</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span> <span class="typ">int</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span> <span class="pun">}</span> |
;
instead of {}
for empty constructor bodies.
DO use Linter rule: empty_constructor_bodies
In Dart, a constructor with an empty body can be terminated with just a semicolon. (In fact, it’s required for const constructors.)
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span> <span class="pun">}</span> |
1 2 3 4 |
<span class="kwd">class</span> <span class="typ">Point</span> <span class="pun">{</span> <span class="typ">int</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span> <span class="typ">Point</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span> <span class="kwd">this</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span> <span class="pun">{}</span> <span class="pun">}</span> |
new
.
DON’T use Linter rule: unnecessary_new
Dart 2 makes the new
keyword optional. Even in Dart 1, its meaning was never clear because factory constructors mean a new
invocation may still not actually return a new object.
The language still permits new
in order to make migration less painful, but consider it deprecated and remove it from your code.
1 2 3 4 5 6 7 8 9 10 |
<span class="typ">Widget</span><span class="pln"> build</span><span class="pun">(</span><span class="typ">BuildContext</span><span class="pln"> context</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span> <span class="typ">Row</span><span class="pun">(</span><span class="pln"> children</span><span class="pun">:</span> <span class="pun">[</span> <span class="typ">RaisedButton</span><span class="pun">(</span><span class="pln"> child</span><span class="pun">:</span> <span class="typ">Text</span><span class="pun">(</span><span class="str">'Increment'</span><span class="pun">),</span> <span class="pun">),</span> <span class="typ">Text</span><span class="pun">(</span><span class="str">'Click!'</span><span class="pun">),</span> <span class="pun">],</span> <span class="pun">);</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 |
<span class="typ">Widget</span><span class="pln"> build</span><span class="pun">(</span><span class="typ">BuildContext</span><span class="pln"> context</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span> <span class="highlight"><span class="kwd">new</span></span> <span class="typ">Row</span><span class="pun">(</span><span class="pln"> children</span><span class="pun">:</span> <span class="pun">[</span> <span class="highlight"><span class="kwd">new</span></span> <span class="typ">RaisedButton</span><span class="pun">(</span><span class="pln"> child</span><span class="pun">:</span> <span class="highlight"><span class="kwd">new</span></span> <span class="typ">Text</span><span class="pun">(</span><span class="str">'Increment'</span><span class="pun">),</span> <span class="pun">),</span> <span class="highlight"><span class="kwd">new</span></span> <span class="typ">Text</span><span class="pun">(</span><span class="str">'Click!'</span><span class="pun">),</span> <span class="pun">],</span> <span class="pun">);</span> <span class="pun">}</span> |
const
redundantly.
DON’T use Linter rule: unnecessary_const
In contexts where an expression must be constant, the const
keyword is implicit, doesn’t need to be written, and shouldn’t. Those contexts are any expression inside:
- A const collection literal.
- A const constructor call
- A metadata annotation.
- The initializer for a const variable declaration.
- A switch case expression—the part right after
case
before the:
, not the body of the case.
(Default values are not included in this list because future versions of Dart may support non-const default values.)
Basically, any place where it would be an error to write new
instead of const
, Dart 2 allows you to omit the const
.
1 2 3 4 5 |
<span class="kwd">const</span><span class="pln"> primaryColors </span><span class="pun">=</span> <span class="pun">[</span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"red"</span><span class="pun">,</span> <span class="pun">[</span><span class="lit">255</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">]),</span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"green"</span><span class="pun">,</span> <span class="pun">[</span><span class="lit">0</span><span class="pun">,</span> <span class="lit">255</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">]),</span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"blue"</span><span class="pun">,</span> <span class="pun">[</span><span class="lit">0</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">,</span> <span class="lit">255</span><span class="pun">]),</span> <span class="pun">];</span> |
1 2 3 4 5 |
<span class="kwd">const</span><span class="pln"> primaryColors </span><span class="pun">=</span> <span class="highlight"><span class="kwd">const</span></span> <span class="pun">[</span> <span class="highlight"><span class="kwd">const</span></span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"red"</span><span class="pun">,</span> <span class="highlight"><span class="kwd">const</span></span> <span class="pun">[</span><span class="lit">255</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">]),</span> <span class="highlight"><span class="kwd">const</span></span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"green"</span><span class="pun">,</span> <span class="highlight"><span class="kwd">const</span></span> <span class="pun">[</span><span class="lit">0</span><span class="pun">,</span> <span class="lit">255</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">]),</span> <span class="highlight"><span class="kwd">const</span></span> <span class="typ">Color</span><span class="pun">(</span><span class="str">"blue"</span><span class="pun">,</span> <span class="highlight"><span class="kwd">const</span></span> <span class="pun">[</span><span class="lit">0</span><span class="pun">,</span> <span class="lit">0</span><span class="pun">,</span> <span class="lit">255</span><span class="pun">]),</span> <span class="pun">];</span> |
Error handling
Dart uses exceptions when an error occurs in your program. The following best practices apply to catching and throwing exceptions.
on
clauses.
AVOID catches without Linter rule: avoid_catches_without_on_clauses
A catch clause with no on
qualifier catches anything thrown by the code in the try block. Pokémon exception handling is very likely not what you want. Does your code correctly handle StackOverflowError or OutOfMemoryError? If you incorrectly pass the wrong argument to a method in that try block do you want to have your debugger point you to the mistake or would you rather that helpful ArgumentError get swallowed? Do you want any assert()
statements inside that code to effectively vanish since you’re catching the thrown AssertionErrors?
The answer is probably “no”, in which case you should filter the types you catch. In most cases, you should have an on
clause that limits you to the kinds of runtime failures you are aware of and are correctly handling.
In rare cases, you may wish to catch any runtime error. This is usually in framework or low-level code that tries to insulate arbitrary application code from causing problems. Even here, it is usually better to catch Exception than to catch all types. Exception is the base class for all runtime errors and excludes errors that indicate programmatic bugs in the code.
on
clauses.
DON’T discard errors from catches without If you really do feel you need to catch everything that can be thrown from a region of code, do something with what you catch. Log it, display it to the user or rethrow it, but do not silently discard it.
Error
only for programmatic errors.
DO throw objects that implement The Error class is the base class for programmatic errors. When an object of that type or one of its subinterfaces like ArgumentError is thrown, it means there is a bug in your code. When your API wants to report to a caller that it is being used incorrectly throwing an Error sends that signal clearly.
Conversely, if the exception is some kind of runtime failure that doesn’t indicate a bug in the code, then throwing an Error is misleading. Instead, throw one of the core Exception classes or some other type.
Error
or types that implement it.
DON’T explicitly catch This follows from the above. Since an Error indicates a bug in your code, it should unwind the entire callstack, halt the program, and print a stack trace so you can locate and fix the bug.
Catching errors of these types breaks that process and masks the bug. Instead of adding error-handling code to deal with this exception after the fact, go back and fix the code that is causing it to be thrown in the first place.
rethrow
to rethrow a caught exception.
DO use Linter rule: use_rethrow_when_possible
If you decide to rethrow an exception, prefer using the rethrow
statement instead of throwing the same exception object using throw
. rethrow
preserves the original stack trace of the exception. throw
on the other hand resets the stack trace to the last thrown position.
1 2 3 4 5 6 |
<span class="kwd">try</span> <span class="pun">{</span><span class="pln"> somethingRisky</span><span class="pun">();</span> <span class="pun">}</span> <span class="kwd">catch</span> <span class="pun">(</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(!</span><span class="pln">canHandle</span><span class="pun">(</span><span class="pln">e</span><span class="pun">))</span> <span class="kwd">throw</span><span class="pln"> e</span><span class="pun">;</span><span class="pln"> handle</span><span class="pun">(</span><span class="pln">e</span><span class="pun">);</span> <span class="pun">}</span> |
1 2 3 4 5 6 |
<span class="kwd">try</span> <span class="pun">{</span><span class="pln"> somethingRisky</span><span class="pun">();</span> <span class="pun">}</span> <span class="kwd">catch</span> <span class="pun">(</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(!</span><span class="pln">canHandle</span><span class="pun">(</span><span class="pln">e</span><span class="pun">))</span> <span class="highlight"><span class="pln">rethrow</span></span><span class="pun">;</span><span class="pln"> handle</span><span class="pun">(</span><span class="pln">e</span><span class="pun">);</span> <span class="pun">}</span> |
Asynchrony
Dart has several language features to support asynchronous programming. The following best practices apply to asynchronous coding.
PREFER async/await over using raw futures.
Asynchronous code is notoriously hard to read and debug, even when using a nice abstraction like futures. The async
/await
syntax improves readability and lets you use all of the Dart control flow structures within your async code.
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">int</span><span class="pun">></span><span class="pln"> countActivePlayers</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> teamName</span><span class="pun">)</span> <span class="highlight"><span class="kwd">async</span></span> <span class="pun">{</span> <span class="kwd">try</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> team </span><span class="pun">=</span> <span class="highlight"><span class="kwd">await</span></span><span class="pln"> downloadTeam</span><span class="pun">(</span><span class="pln">teamName</span><span class="pun">);</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">team </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span> <span class="kwd">return</span> <span class="lit">0</span><span class="pun">;</span> <span class="kwd">var</span><span class="pln"> players </span><span class="pun">=</span> <span class="highlight"><span class="kwd">await</span></span><span class="pln"> team</span><span class="pun">.</span><span class="pln">roster</span><span class="pun">;</span> <span class="kwd">return</span><span class="pln"> players</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">player</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> player</span><span class="pun">.</span><span class="pln">isActive</span><span class="pun">).</span><span class="pln">length</span><span class="pun">;</span> <span class="pun">}</span> <span class="kwd">catch</span> <span class="pun">(</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> log</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">e</span><span class="pun">);</span> <span class="kwd">return</span> <span class="lit">0</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">int</span><span class="pun">></span><span class="pln"> countActivePlayers</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> teamName</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span><span class="pln"> downloadTeam</span><span class="pun">(</span><span class="pln">teamName</span><span class="pun">).</span><span class="pln">then</span><span class="pun">((</span><span class="pln">team</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">team </span><span class="pun">==</span> <span class="kwd">null</span><span class="pun">)</span> <span class="kwd">return</span> <span class="typ">Future</span><span class="pun">.</span><span class="pln">value</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> team</span><span class="pun">.</span><span class="pln">roster</span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">players</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span><span class="pln"> players</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">player</span><span class="pun">)</span> <span class="pun">=></span><span class="pln"> player</span><span class="pun">.</span><span class="pln">isActive</span><span class="pun">).</span><span class="pln">length</span><span class="pun">;</span> <span class="pun">});</span> <span class="pun">}).</span><span class="pln">catchError</span><span class="pun">((</span><span class="pln">e</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> log</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">e</span><span class="pun">);</span> <span class="kwd">return</span> <span class="lit">0</span><span class="pun">;</span> <span class="pun">});</span> <span class="pun">}</span> |
async
when it has no useful effect.
DON’T use It’s easy to get in the habit of using async
on any function that does anything related to asynchrony. But in some cases, it’s extraneous. If you can omit the async
without changing the behavior of the function, do so.
1 2 3 |
<span class="typ">Future</span><span class="pln"> afterTwoThings</span><span class="pun">(</span><span class="typ">Future</span><span class="pln"> first</span><span class="pun">,</span> <span class="typ">Future</span><span class="pln"> second</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span> <span class="typ">Future</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">([</span><span class="pln">first</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">]);</span> <span class="pun">}</span> |
1 2 3 |
<span class="typ">Future</span><span class="pln"> afterTwoThings</span><span class="pun">(</span><span class="typ">Future</span><span class="pln"> first</span><span class="pun">,</span> <span class="typ">Future</span><span class="pln"> second</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">return</span> <span class="typ">Future</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">([</span><span class="pln">first</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">]);</span> <span class="pun">}</span> |
Cases where async
is useful include:
- You are using
await
. (This is the obvious one.) - You are returning an error asynchronously.
async
and thenthrow
is shorter thanreturn Future.error(...)
. - You are returning a value and you want it implicitly wrapped in a future.
async
is shorter thanFuture.value(...)
.
1 2 3 4 5 6 7 8 9 |
<span class="typ">Future</span><span class="pln"> usesAwait</span><span class="pun">(</span><span class="typ">Future</span><span class="pln"> later</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="kwd">await</span><span class="pln"> later</span><span class="pun">);</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pln"> asyncError</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">throw</span> <span class="str">'Error!'</span><span class="pun">;</span> <span class="pun">}</span> <span class="typ">Future</span><span class="pln"> asyncValue</span><span class="pun">()</span> <span class="kwd">async</span> <span class="pun">=></span> <span class="str">'value'</span><span class="pun">;</span> |
CONSIDER using higher-order methods to transform a stream.
This parallels the above suggestion on iterables. Streams support many of the same methods and also handle things like transmitting errors, closing, etc. correctly.
AVOID using Completer directly.
Many people new to asynchronous programming want to write code that produces a future. The constructors in Future don’t seem to fit their need so they eventually find the Completer class and use that.
1 2 3 4 5 6 7 8 9 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">bool</span><span class="pun">></span><span class="pln"> fileContainsBear</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> path</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> completer </span><span class="pun">=</span> <span class="typ">Completer</span><span class="pun"><</span><span class="typ">bool</span><span class="pun">>();</span> <span class="typ">File</span><span class="pun">(</span><span class="pln">path</span><span class="pun">).</span><span class="pln">readAsString</span><span class="pun">().</span><span class="pln">then</span><span class="pun">((</span><span class="pln">contents</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> completer</span><span class="pun">.</span><span class="pln">complete</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'bear'</span><span class="pun">));</span> <span class="pun">});</span> <span class="kwd">return</span><span class="pln"> completer</span><span class="pun">.</span><span class="pln">future</span><span class="pun">;</span> <span class="pun">}</span> |
Completer is needed for two kinds of low-level code: new asynchronous primitives, and interfacing with asynchronous code that doesn’t use futures. Most other code should use async/await or Future.then()
, because they’re clearer and make error handling easier.
1 2 3 4 5 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">bool</span><span class="pun">></span><span class="pln"> fileContainsBear</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> path</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span> <span class="typ">File</span><span class="pun">(</span><span class="pln">path</span><span class="pun">).</span><span class="pln">readAsString</span><span class="pun">().</span><span class="pln">then</span><span class="pun">((</span><span class="pln">contents</span><span class="pun">)</span> <span class="pun">{</span> <span class="kwd">return</span><span class="pln"> contents</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'bear'</span><span class="pun">);</span> <span class="pun">});</span> <span class="pun">}</span> |
1 2 3 4 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">bool</span><span class="pun">></span><span class="pln"> fileContainsBear</span><span class="pun">(</span><span class="typ">String</span><span class="pln"> path</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> contents </span><span class="pun">=</span> <span class="kwd">await</span> <span class="typ">File</span><span class="pun">(</span><span class="pln">path</span><span class="pun">).</span><span class="pln">readAsString</span><span class="pun">();</span> <span class="kwd">return</span><span class="pln"> contents</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'bear'</span><span class="pun">);</span> <span class="pun">}</span> |
Future<T>
when disambiguating a FutureOr<T>
whose type argument could be Object
.
DO test for Before you can do anything useful with a FutureOr<T>
, you typically need to do an is
check to see if you have a Future<T>
or a bare T
. If the type argument is some specific type as in FutureOr<int>
, it doesn’t matter which test you use, is int
or is Future<int>
. Either works because those two types are disjoint.
However, if the value type is Object
or a type parameter that could possibly be instantiated with Object
, then the two branches overlap. Future<Object>
itself implements Object
, so is Object
or is T
where T
is some type parameter that could be instantiated with Object
returns true even when the object is a future. Instead, explicitly test for the Future
case:
1 2 3 4 5 6 7 8 9 10 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">T</span><span class="pun">></span><span class="pln"> logValue</span><span class="pun"><</span><span class="typ">T</span><span class="pun">>(</span><span class="typ">FutureOr</span><span class="pun"><</span><span class="typ">T</span><span class="pun">></span><span class="pln"> value</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">value </span><span class="kwd">is</span> <span class="typ">Future</span><span class="pun"><</span><span class="typ">T</span><span class="pun">>)</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> result </span><span class="pun">=</span> <span class="kwd">await</span><span class="pln"> value</span><span class="pun">;</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">result</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> result</span><span class="pun">;</span> <span class="pun">}</span> <span class="kwd">else</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> value </span><span class="kwd">as</span> <span class="typ">T</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
1 2 3 4 5 6 7 8 9 10 |
<span class="typ">Future</span><span class="pun"><</span><span class="typ">T</span><span class="pun">></span><span class="pln"> logValue</span><span class="pun"><</span><span class="typ">T</span><span class="pun">>(</span><span class="typ">FutureOr</span><span class="pun"><</span><span class="typ">T</span><span class="pun">></span><span class="pln"> value</span><span class="pun">)</span> <span class="kwd">async</span> <span class="pun">{</span> <span class="kwd">if</span> <span class="pun">(</span><span class="pln">value </span><span class="kwd">is</span> <span class="typ">T</span><span class="pun">)</span> <span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> value</span><span class="pun">;</span> <span class="pun">}</span> <span class="kwd">else</span> <span class="pun">{</span> <span class="kwd">var</span><span class="pln"> result </span><span class="pun">=</span> <span class="kwd">await</span><span class="pln"> value</span><span class="pun">;</span><span class="pln"> print</span><span class="pun">(</span><span class="pln">result</span><span class="pun">);</span> <span class="kwd">return</span><span class="pln"> result</span><span class="pun">;</span> <span class="pun">}</span> <span class="pun">}</span> |
In the bad example, if you pass it a Future<Object>
, it incorrectly treats it like a bare, synchronous value.