Despite a rocky start with Dart, I'm still really enjoying the language. When I get started with any new programming language I like to compile a "Quick Tips" document to help me get around common pitfalls and old manners of thinking. Usually I just leave them to rot in my OneNote app, but for Dart I'm publishing them here.
This post is by no means meant to be comprehensive, and the format is probably not that great for consumption. These are loosely organized notes that I append to a OneNote document when I think it will be useful for my future self.
- Make use of the analysis_options.yaml file! It's documented here: https://www.dartlang.org/guides/language/analysis-options. Specifically, set
analyzer: strong-mode: true
which will make the VS Code extension use strong mode to check your code. Strong mode catches errors like this:abstract class Airborne { void Fly; } class Bird implements Airborne { void Fly = () => print("Bird is flying"); } class Duck implements Airborne { void Fly = () => print("Duck is flying"); } class Whale { void Swim = () => print("Whale is swimming"); } void startFlying<T extends Airborne>(T entity) { Entity.Fly(); } void main() { startFlying(new Bird()); startFlying(new Duck()); // This *will* pass and compile if you aren't using strong mode, but it will fail to compile if you *are* using it. startFlying(new Whale()); }
- Use the
dartanalyzer
executable to analyze and lint your Dart project. Either usedartanalyzer --strong path-to-file.dart
, or create ananalysis_options.yaml
file withanalyzer: string: true
set. This will turn on strong mode (which will be the default in Dart 2.0), catching many (all?) type errors. - This is my current analyzer and linter rules file. Using this gives a fairly C#-y strong rule set that won't let you do too many crazy dynamic things that might cause runtime errors.
- Options that you should use in your analyzer errors list in analysis_options.yaml:
undefined_identifier
: prevents you from using variables and identifiers that haven't been defined or have since been removed (such as when you remove a package import).undefined_class
: prevents you from attempting to use an undefined class.undefined_method
: prevents you from attempting to use an undefined method.undefined_function
: prevents you from attempting to use an undefined function.missing_return
: prevents you from specifying a return type but never actually returning.return_of_invalid_type
: prevents you from returning e.g. aString
from a method that says it returns anint
.not_enough_required_arguments
: prevents you from calling a function without all of the required arguments.no_adjacent_strings_in_list
: prevents you from doingvar list = <String> ["a" "b", "c"]
with no comma between "a" and "b" which would produce a list that looks like ["ab", "c"].
- When you see something that you think should be an error but only gives you a warning, run the
dartanalyzer
on the file. When it lists the warning in its output you'll see the rule's name at the end of the line in the format of "warning - This is a description of the warning - rule_name_here". Add that to youranalyzer.errors
list in analysis_options.yaml to turn it into an error. - Linter and analyzer rules that I'm still missing:
- ~~
all_code_paths_must_return
: an analyzer rule that ensures all code paths return a value. E.g.
String myFunc() { if (condition == true) return ""; } should create an error because it returns nothing ifcondition == false
.~~ Nevermind! It's smart enough to know that writingif (true) return ""
will always return. Try it with something it can't figure out likeif (condition()) return ""
and it will properly lint.
- ~~
- Quick solution for the JSON.encode error "Converting object to an encodable object failed.": pass a
toEncodable
function that first checks if the argument received is anIterable
and then converts it to a List, then tries to return the result of callingarg.toJson()
.- For whatever reason the JSON codec can convert a list but it won't try to convert an iterable. It also seems to think that an actual list found in a Map object is actually an iterable and not a list and therefore won't try to encode it.
- Return the result of
arg.toJson()
or else your root object won't be converted either. This way you don't need to manually convert child classes either in your customtoJson
calls, rather the codec and thetoEncodable
function will do it for you.const Map<String, dynamic> myBird = { "name": "Blue Jay", "species": "Cyanocitta cristata", "someList": [...] } dynamic toEncodable(arg) { if (arg is Iterable) { // Convert arg to List as JSON codec doesn't know how to encode an Iterable return arg.toList(); } // Call toJson here in case the object has that method implemented. If not an error will be thrown. // An error will throw regardless because the JSON codec won't know how to encode this prop. return arg.toJson(); } void main() { // This will fail because JSON codec doesn't know how to encode the "someList" Iterable. var thisWillFail = JSON.encode(myBird); // This will pass because our toEncodable function converts Iterable to List. var thisWillEncode = JSON.encode(myBird, toEncodable: toEncodable); }
- When deserializing with dson's
fromJson
, the class you're deserializing to must either have a constructor with 0 arguments, or the fields in the constructor must be final.class Bird extends _$BirdSerializable { String name; String species; Bird(); } // Or class Bird extends _$BirdSerializable { final String name; final String species; Bird(this.name, this.species); }
- Dart seems to have two conventional build systems,
builder
scripts andpub build
transformer scripts. I'm not sure which to use right now, but I've gone with builder scripts becausepub build
seems to be intended for the web, forcing you to have aweb
directory and outputting your built files to abuilt
folder. - With
builder
scripts keep in mind that they're not very similar to e.g. NPM scripts in that it's difficult to customize them. You can create your ownBuilder
andBuildAction
to do some pretty powerful things (such as compile JSON helpers, explained below), but noBuildAction
can output the same file that a previous one did. That is, only oneBuildAction
can output an e.g.main.g.dart
file. Another action could read that file, but it would have to output one with a different name.- This isn't true for Pub transformers, though. The problem is Pub forces you to output your files into a
built
folder which I think is meant for web projects only.
- This isn't true for Pub transformers, though. The problem is Pub forces you to output your files into a
- I'm using a
builder
script to compile JSON helpers for my serializable classes with thedson
package. It's sadly necessary because of the poor state of JSON support in Dart, but thankfully once setup it's a decently seamless experience. Here are the instructions for that:- Install the dson package (preferably my fork, explained below) by adding it to your
pubspec.yaml
file. - Create a
tool
directory and copy over the three files from this repo. - Import
package:dson/dson.dart
in your file containing the classes you want to be serializable and add@serializable
to those classes. - Make your classes extend the generated serializable class.
@serializable class Bird extends _$BirdSerializable { ... } @serializable class Fox extends _$FoxSerializable { ... }
- Call
_initMirrors()
at your application's entry point. Dson serialization/deserialization won't work without it. - Run the build script:
dart tool/build.dart
. Dson will generate files namedfile.g.dart
and your app should now compile. - Linter rules go crazy on the generated files. I had to fork the package and add an extra parameter to the
dsonBuilder
that would let me pass a custom header string to the generated files. Using that header string I'm injecting the comment// ignore_for_file: always_declare_return_types
which turns off that linting rule for only the generated files. If you want to use my fork to get this header, just add the following to your pubspec.yaml:dependencies: ... dson: git: url: git://github.com/nozzlegear/dson.git ref: c91a77acd60851
- Finally, to encode files to JSON just
import dart:convert show JSON
and useJSON.encode(new Bird(), toEncodable: functionFromAbove)
. - To decode JSON to a class, use the
fromJson
function (imported frompackage:dson/dson.dart
) in this way:Bird myBird = fromJson(jsonString, Bird)
. If you're deserializing to a list you'll need to pass an array of types like so:List<Bird> myBirds = fromJson(jsonString, [List, Bird])
.
- Install the dson package (preferably my fork, explained below) by adding it to your
Learn how to build rock solid Shopify apps with C# and ASP.NET!
Did you enjoy this article? I wrote a premium course for C# and ASP.NET developers, and it's all about building rock-solid Shopify apps from day one.
Enter your email here and I'll send you a free sample from The Shopify Development Handbook. It'll help you get started with integrating your users' Shopify stores and charging them with the Shopify billing API.