Quick tips for Dart

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 use dartanalyzer --strong path-to-file.dart, or create an analysis_options.yaml file with analyzer: 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. a String from a method that says it returns an int.
    • not_enough_required_arguments: prevents you from calling a function without all of the required arguments.
    • no_adjacent_strings_in_list: prevents you from doing var 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 your analyzer.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 if condition == false.~~ Nevermind! It's smart enough to know that writing if (true) return "" will always return. Try it with something it can't figure out like if (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 an Iterable and then converts it to a List, then tries to return the result of calling arg.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 custom toJson calls, rather the codec and the toEncodable 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 and pub build transformer scripts. I'm not sure which to use right now, but I've gone with builder scripts because pub build seems to be intended for the web, forcing you to have a web directory and outputting your built files to a built 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 own Builder and BuildAction to do some pretty powerful things (such as compile JSON helpers, explained below), but no BuildAction can output the same file that a previous one did. That is, only one BuildAction 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.
  • I'm using a builder script to compile JSON helpers for my serializable classes with the dson 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 named file.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 use JSON.encode(new Bird(), toEncodable: functionFromAbove).
    • To decode JSON to a class, use the fromJson function (imported from package: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]).

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.

We won't send you spam. Unsubscribe at any time.