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?
Making Builders from the Generators
In build.yaml, you configured build_runner
to look for builder.dart. So, as a last step, create builder.dart in lib and add this code:
// 1
import 'package:build/build.dart';
// 2
import 'package:source_gen/source_gen.dart';
// 3
import 'src/extension_generator.dart';
import 'src/subclass_generator.dart';
// 4
Builder generateExtension(BuilderOptions options) =>
SharedPartBuilder([ExtensionGenerator()], 'extension_generator');
Builder generateSubclass(BuilderOptions options) =>
SharedPartBuilder([SubclassGenerator()], 'subclass_generator');
Here’s what the code does:
- You import
build
to get access toBuilder
. This base class is responsible for generating files from existing ones. -
source_gen
provides some pre-implemented builders that cover common use cases of code generation. In this case, you needSharedPartBuilder
, which renderspart of
files. - Here, you import the generators you created above.
- These functions return the
Builder
for each of the two generators.SharedPartBuilder
takes a list of generators as parameters to generate the code. To make each builder unique, you also need to provide an identifier. These functions are simple and apt for this use case, but you always have the power to configure theBuilder
more throughBuilderOptions
.
part of
is a directive of dart that allows you to access private variables or methods from another file.Hurray! This completes both generators. Now, it’s time to test them out!
Testing the Generators
As mentioned before, you’ll use the example project to test the generated code. Open it in your preferred IDE and look at the dependencies in the project’s pubspec.yaml:
dependencies:
annotations:
path: ../annotations/
dev_dependencies:
build_runner:
generators:
path: ../generators/
The file in the starter project already includes your annotations
and generators
, as well as the needed build_runner
.
annotations
is a dependency you’ll use during the compilation of the project. build_runner
and generators
are dev_dependencies
because you only use them during the development process.
Get the dependencies by clicking the Get packages button, or however you do this in your IDE.
Now, create the model you’ll generate getters and setters for. Head over to lib and create profile_model.dart, like this:
// 1
import 'package:annotations/annotations.dart';
// 2
part 'profile_model.g.dart';
// 3
@generateSubclass
class ProfileModel {
// 4
String _name = 'Aachman';
int _age = 20;
bool _codes = true;
}
Here’s what this code does:
- First, you import the annotations package.
- You add
part 'profile_model.g.dart';
to include the generated file as a part of the original file. - Using the annotation
@generateSubclass
, you triggerSubclassGenerator
to generate code. - Note that all fields are private. The generated code will make them public.
You can ignore the error message Target of URI hasn’t been generated: ‘profile_model.g.dart’. This is a typical error message when using generated code that you haven’t generated yet.
Generating the Code
Now, the moment you’ve been waiting for has come. It’s time to generate code!
Run this command in the terminal:
flutter pub run build_runner build
You’ll see something like this in the terminal console log:
[INFO] Generating build script...
[INFO] Generating build script completed, took 474ms
[INFO] Creating build script snapshot......
[INFO] Creating build script snapshot... completed, took 13.8s
[INFO] Initializing inputs
[INFO] Building new asset graph...
[INFO] Building new asset graph completed, took 793ms
[INFO] Checking for unexpected pre-existing outputs....
[INFO] Checking for unexpected pre-existing outputs. completed, took 1ms
[INFO] Running build...
[INFO] Generating SDK summary...
[INFO] 4.3s elapsed, 0/2 actions completed.
[INFO] Generating SDK summary completed, took 4.3s
[INFO] Running build completed, took 5.0s
[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 51ms
[INFO] Succeeded after 5.0s with 2 outputs (7 actions)
Congrats! You should see a new file named profile_model.g.dart generated in lib. Cross-check if it looks like this:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'profile_model.dart';
// **************************************************************************
// SubclassGenerator
// **************************************************************************
class ProfileModelGen extends ProfileModel {
Map<String, dynamic> variables = {};
ProfileModelGen() {
variables['name'] = super._name;
variables['age'] = super._age;
variables['codes'] = super._codes;
}
String get name => variables['name'];
set name(String name) {
super._name = name;
variables['name'] = name;
}
int get age => variables['age'];
set age(int age) {
super._age = age;
variables['age'] = age;
}
bool get codes => variables['codes'];
set codes(bool codes) {
super._codes = codes;
variables['codes'] = codes;
}
}
OK, now go to main.dart and test out the generated model. First, import your model:
import 'profile_model.dart';
Then, right under _ProfilePageState
, add a line like this:
class _ProfilePageState extends State<ProfilePage> {
ProfileModelGen profile = ProfileModelGen();
...
}
Here, you create an instance of ProfileModelGen
that you want to test.
Now, search for the string ‘?’ in a Text
widget and change it like this:
child: Text(profile.name.substring(0, 1),
Here, you use the generated getter for the name
variable instead of '?'
.
Here’s another test you can run: Check if the variables
map contains all variables of the model. Search for the comment // TODO Display the values in the map and replace it, like so:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ // TODO Display the values in the map
// 1
for (String key in profile.variables.keys.toList())
RichText(
text: TextSpan(children: [
TextSpan(
// 2
text: '$key: '.toUpperCase(),
style: TextStyle(
fontSize: 24,
color: Colors.grey[600],
),
),
TextSpan(
// 3
text: '${profile.variables[key]}',
style: TextStyle(
fontSize: 36,
color: Colors.green[200],
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
),
),
]),
)
],
)
Here’s what the code above does:
- The
for loop
iterates over all the keys of the profile’svariables
map. - The first part of
RichText
displays the key, which is the name of the variable. - The second part displays the stored value for the variable.
Build and run by entering flutter run in the terminal. The app will look like this:
Bravo! This means that SubclassGenerator
works perfectly.
To test the second generator, just change the annotation to @generateExtension
:
@generateExtension
class ProfileModel {
String _name = 'Aachman';
int _age = 20;
bool _codes = true;
}
You need to generate the file again using the same command. However, since a generated file is already there, you need to delete it first. Don’t do that manually; instead, add –delete-conflicting-outputs to the command.
flutter pub run build_runner build --delete-conflicting-outputs
The console output is the same as for SubclassGenerator, but the newly generated file will look different:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'profile_model.dart';
// **************************************************************************
// ExtensionGenerator
// **************************************************************************
extension GeneratedModel on ProfileModel {
Map<String, dynamic> get variables => {
'name': _name,
'age': _age,
'codes': _codes,
};
String get name => variables['name'];
set name(String name) => _name = name;
int get age => variables['age'];
set age(int age) => _age = age;
bool get codes => variables['codes'];
set codes(bool codes) => _codes = codes;
}
In main.dart, replace ProfileModelGen
with ProfileModel
and nothing else:
ProfileModel profile = ProfileModel();
Build and run! Everything should still work the same.
You did it! You just built a code generation library from scratch. Feel free to explore other APIs offered by source_gen and build to create even more powerful code generation tools.