Codegen
- Dart course
- Expert
- Lesson 45/47
- 6 min
Write code generation with build_runner.
Codegen
Now we add code generation. We will define a NativeBinding annotation class and a BindingGenerator class. This is a simplified model of the build_runner + source_gen approach.
NativeBinding marks a class as having a native binding. BindingGenerator produces binding code for the marked class. In reality build_runner does this automatically; here we call it manually.
In this lesson gen.generate('NativeMathService') returns the generated binding code as a String. This String represents the code that would be written to a .g.dart file.
The annotation class
In Dart annotations are classes with a const constructor. class NativeBinding { final String name; const NativeBinding(this.name); } allows @NativeBinding('native_add') usage.
BindingGenerator takes a class name and produces binding code: String generate(String className) => '// generated binding for $className';. In reality this would be a full class wrapping FFI calls.
This model mirrors source_gen's GeneratorForAnnotation approach. Annotation-marked classes are found automatically and code is generated. Here we call it manually but the mechanism is the same.
Practice Add NativeBinding and BindingGenerator
The NativeMathService is already defined below. Now define a class NativeBinding with final String name; const NativeBinding(this.name);. Define a class BindingGenerator with String generate(String className) => '// generated binding for $className';. In main create final gen = BindingGenerator(); and add print(gen.generate('NativeMathService'));.
Starter code
import 'dart:ffi';
abstract class MathService {
int add(int a, int b);
}
typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);
class NativeMathService implements MathService {
final String bindingName;
NativeMathService(this.bindingName);
@override
int add(int a, int b) => a + b;
String describe() => 'native: $bindingName';
}
// define a NativeBinding annotation class (const constructor, name field)
// define a BindingGenerator class (generate method)
void main() {
print('service ready');
final svc = NativeMathService('native_add');
print(svc.describe());
print('result: ${svc.add(2, 3)}');
// create a BindingGenerator; print via generate
}Sample solution
import 'dart:ffi';
abstract class MathService {
int add(int a, int b);
}
typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);
class NativeMathService implements MathService {
final String bindingName;
NativeMathService(this.bindingName);
@override
int add(int a, int b) => a + b;
String describe() => 'native: $bindingName';
}
class NativeBinding {
final String name;
const NativeBinding(this.name);
}
class BindingGenerator {
String generate(String className) => '// generated binding for $className';
}
void main() {
print('service ready');
final svc = NativeMathService('native_add');
print(svc.describe());
print('result: ${svc.add(2, 3)}');
final gen = BindingGenerator();
print(gen.generate('NativeMathService'));
}Expected output
service ready native: native_add result: 5 // generated binding for NativeMathService
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 What does BindingGenerator produce?
- Test code
- Binding code Correct answer
- UI widgets
- Database table
Explanation: BindingGenerator produces native binding code.
2 The const constructor
class NativeBinding {
final String name;
____ NativeBinding(this.name);
}
class NativeBinding {
final String name;
const NativeBinding(this.name);
}
Explanation: a const constructor can be used as an annotation.
3 Match the codegen parts
| Term | Meaning |
|---|---|
NativeBinding |
Annotation |
BindingGenerator |
Generator class |
generate(...) |
Generation method |
.g.dart |
Generated file |