Introduction
Metaprogramming is a powerful programming technique where code can inspect, modify, or generate other code at runtime or compile-time. While Dart is known for its simplicity and safety, it also supports metaprogramming — and with Dart 3’s upcoming macros, it’s evolving rapidly.
This guide will walk you through:
- What metaprogramming is
- How to use reflection in Dart
- How code generation works using
build_runner
- What to expect from Dart macros in the near future
If you’re a Flutter or backend Dart developer looking to optimize, automate, or scale your code — mastering metaprogramming in Dart will unlock powerful new tools.
What Is Metaprogramming?
Metaprogramming is the ability of a program to:
- Analyze itself (introspection)
- Modify its behavior dynamically
- Generate additional code based on annotations or input
Common Use Cases:
- Automatic serialization/deserialization (e.g., JSON)
- Dependency injection
- Code analysis and cleanup
- Generating boilerplate code (e.g., copyWith, equality)
1. Reflection in Dart
Dart provides basic reflection capabilities via the dart:mirrors
library — but with limitations.
Example:
import 'dart:mirrors';
void main() {
var instance = MyClass();
var mirror = reflect(instance);
print(mirror.type.simpleName); // Symbol("MyClass")
}
class MyClass {
void sayHello() => print("Hello!");
}
Limitations:
dart:mirrors
is not supported in Flutter.- It’s only usable in standalone Dart CLI/server apps.
- It increases binary size and complicates tree-shaking.
For Flutter, use code generation instead.
2. Code Generation with build_runner
Since Flutter apps cannot use runtime reflection, Dart offers compile-time code generation using annotations + builders.
Popular Tools:
json_serializable
– for object <-> JSON mappingfreezed
– for union types and immutable modelsinjectable
– for dependency injection
Example: Using json_serializable
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final String name;
final int age;
User({required this.name, required this.age});
factory User.fromJson(Map<String, dynamic> json) =>
_$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Command to generate code:
flutter pub run build_runner build
Advantages of Code Generation:
- No runtime overhead
- Works in all Dart environments (Flutter, CLI, Web)
- Strongly typed
- IDE-friendly and maintainable
3. The Future: Dart Macros (Preview in Dart 3.x+)
Dart Macros are the future of metaprogramming in Dart, allowing inline code transformation at compile-time — similar to what TypeScript decorators or Rust macros do.
What Are Macros?
Macros let you:
- Generate code using compile-time logic
- Replace boilerplate like
copyWith
,toJson
,equals
- Access AST (abstract syntax tree) safely
- Work without build_runner
Example (conceptual, experimental API):
macro class Data {}
@Data()
class User {
final String name;
final int age;
}
This macro could automatically generate:
- A constructor
copyWith()
toJson()
/fromJson()
==
andhashCode
All at compile-time, natively — no build_runner
required.
Status:
- Experimental (in SDK preview builds)
- Aims to stabilize in future Dart 3.x versions
- Will revolutionize package design and tooling
When Should You Use Metaprogramming in Dart?
Use Case | Technique |
---|---|
CLI or Dart server inspecting types | dart:mirrors |
Flutter app generating model code | build_runner + json_serializable |
Creating immutable data models | freezed or sealed_unions |
Building reusable annotations | Code generation |
Compile-time optimization & abstraction | Dart macros (future) |
Pitfalls & Cautions
- Avoid reflection in production apps (due to performance)
- Code generation can become complex — manage dependencies carefully
- Dart macros are still experimental — avoid in production unless sandboxed
- Always document generated code for team understanding
Read Articles : How to Debounce a Function in Dart ?(Perfect for Search Input in Flutter)
FAQ – Dart Metaprogramming
Metaprogramming in Dart allows programs to analyze or generate code. It’s done using reflection (dart:mirrors
), code generation (build_runner
), or experimental macros.
No. dart:mirrors
is not supported in Flutter. Instead, use code generation packages like json_serializable
, freezed
, or injectable
.
Yes. Dart macros are being developed to support native compile-time code generation without external builders.
Yes — especially in Flutter. Code generation is safer, faster, and tree-shakable.
json_serializable
(for JSON)freezed
(for unions + data classes)injectable
(for DI)retrofit
, built_value
, etc. (depending on use case)
Metaprogramming is one of Dart’s most powerful — and evolving — capabilities. Whether you’re building scalable Flutter UIs or backend tooling, knowing how to use reflection, code generation, and anticipate the rise of macros will help you write smarter, cleaner, and more scalable code.
Don’t wait for macros to become stable — start exploring metaprogramming in Dart today with tools like build_runner
, and prepare your architecture for the future of Dart.
Read Articles : What is Lexical scope and lexical closures in Dart?