Flutter Code Generation: Getting Started
Learn how to use code generation to automatically create Dart models, eliminating tedious and repetitive tasks. By Aachman Garg.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter Code Generation: Getting Started
25 mins
- Getting Started
- Why Code Generation?
- Setting up Your Initial Project
- The Annotations Folder
- The Generators Folder
- Build.yaml
- The Example Folder
- Creating the Necessary Annotations
- Creating the Annotation Classes
- Creating the Generators
- Finding Annotated Classes With ModelVisitor
- Implementing a Generator for a Subclass
- Implementing a Generator for an Extension
- Making Builders from the Generators
- Testing the Generators
- Generating the Code
- Where to Go From Here?
In software engineering, one of the key principles to maximize productivity is to never repeat yourself. For rapid and efficient development, you should automate repetitive tasks. Imagine having to write getters and setters for each model in a project with 100 models, manually serializing JSON or writing a clone for every class. That seems pretty exhausting. Enter code generation.
Code generation of Flutter models helps you automate these tedious tasks so you can focus on what’s important. All you have to do is write the repetitive code pattern once and create a generator, which generates files with code based on the pattern you defined.
In this tutorial, you’ll build a code generator that finds all the variables of a class, stores them in a map and generates getters and setters for saving to and reading from the map. You’ll also learn the following along the way:
- When to use code generation.
- How to define and use annotations.
- How a generator writes code.
- What to use
ElementVisitor
for. - How to configure a
Builder
with build.yaml. - How to generate the models in a Flutter project.
So, without any further delay, get started!
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial. Once downloaded, open the project in your IDE of choice and explore it.
Take a look at the folder structure — it’s essential for the project. You’ll take a closer look at this structure in the coming sections, but first, you should dive a bit into the theory.
Why Code Generation?
Code generation has several advantages under certain situations. Here are a few:
- Data classes: These type of classes are fairly simple and you usually need to create a lot of them. So, it’s a good idea to generate these instead of manually writing each one.
- Architecture boilerplate: Almost every architecture solution comes with some amount of boilerplate code. Writing it repeatedly is a headache that you can prevent, in large part, by generating the code. MobX is a good example of this.
-
Common features/functions: Almost every model class uses certain functions, like
fromMap
,toMap
andcopyWith
. With code generation, the classes can all get these functions in a single command.
Code generation not only saves time and effort, but also improves code quality in terms of consistency and the number of errors. You can literally open any generated file with the assurance that it’ll work perfectly.
Setting up Your Initial Project
The project’s folder structure includes three top level folders: annotations, generators and example. You’ll take a look at each one next.
The Annotations Folder
Inside annotations is a lib folder, which will contain the Dart files for the annotations library. Its src folder will contain files that declare annotations the generators will use. Finally, pubspec.yaml defines the metadata for the annotations library. It’s very simple, with test: 1.3.4
as the only dependency it needs.
The Generators Folder
The lib folder contains the src subfolder for better organization of builder, model_visitor and generator files. More on these in a later section.
Take a look at pubspec.yaml:
name: generators
description: Getters and setters to save variables to and from Map
version: 1.0.0
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
# 1
build:
# 2
source_gen:
# 3
annotations:
path: ../annotations/
dev_dependencies:
# 4
build_runner:
# 5
build_test:
test: ^1.0.0
Here’s what’s going on above:
- build: A package that lets you inspect classes. It gives you access to different elements of a class like variables, methods and constructors. You’ll use this to extract the variable names for generating getters and setters in your project.
-
source_gen: An API providing various utilities that help generate code without much interaction with the low level
build
oranalyzer
packages. This will make your life a lot easier. - annotations: The library you’ll create soon. The generators use it to recognize which classes to process.
-
build_runner: Allows the generation of files from Dart code. This is a
dev_dependency
; you’ll only use it during development. - build_test and test: For testing the generators. You won’t use them in this tutorial.
Build.yaml
There’s one more file to be aware of: build.yaml. Here’s how it looks:
targets:
$default:
builders:
generators|annotations:
enabled: true
builders:
generators:
target: ":generators"
# 1
import: "package:generators/builder.dart"
# 2
builder_factories: ["generateSubclass", "generateExtension"]
# 3
build_extensions: { ".dart": [".g.dart"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
This is the configuration that build_runner needs to find the generators. Here are some important things to note:
-
import
is the path to builder.dart, which you’ll create in a later section. -
builder_factories
contains the method names of global functions that return generators. You’ll define them later. - In
build_extensions
, you specify the extension of the generated file — “.g.dart”, in this case.
The Example Folder
example is the Flutter project in which you’ll use the generator. For now, just leave it and focus on the other two folders.
Creating the Necessary Annotations
Annotations are data classes that specify additional information about a code component. They provide a way to add metadata to code elements like a class, method or variable.
Take, for example, the @override
annotation, which you use when implementing a method from a parent class in a child class. By using it, you’re explicitly specifying that this method is from the parent class.
@
sign.Creating the Annotation Classes
Go to annotations/lib/src and create a new file named subclass_method.dart. Next, add the following code:
class SubclassAnnotation {
const SubclassAnnotation();
}
const generateSubclass = SubclassAnnotation();
The class is blank because, in this project’s use case, you don’t need any additional data in the annotation. The global variable generateSubclass
is the name of the annotation. You’ll use this name to mark a class for a generator. You can create annotations from any class that has a const
constructor.
Similarly, create another file named extension_method.dart in the same folder and add the following code:
class ExtensionAnnotation {
const ExtensionAnnotation();
}
const generateExtension = ExtensionAnnotation();
At this point, you’ve written both annotations, but you’re missing one final step. Create annotations.dart in lib with the following code:
library annotations;
export 'src/subclass_method.dart';
export 'src/extension_method.dart';
In the code above, you export the two files of the package.
Awesome! The annotations library is complete. Next, you’ll move on to creating the generators.