CodeBlanks Logo Icon CodeBlanks

Codegen

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

main.dart
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

main.dart
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

Question
class NativeBinding {
  final String name;
  ____ NativeBinding(this.name);
}
Answer
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

All lessons in Expert