Welcome to the future of Flutter development! Imagine a world where the tedious task of writing boilerplate code is a thing of the past. Say hello to Dart Macros – your new secret weapon for supercharging productivity. These powerful runtime code generation tools work their magic behind the scenes, seamlessly reducing boilerplate and eliminating the need for cumbersome secondary tools. With Flutter macros, you can focus on crafting beautiful, high-performance apps while the macros handle the repetitive tasks, making your development process faster and more efficient than ever before. Get ready to unlock a new level of coding freedom and efficiency with Flutter macros!
A Dart macro is a customisable code snippet that takes other code as input and processes it in real-time to create, modify, or add new elements. Macros offer a reusable way to handle repetitive tasks and patterns, especially when dealing with the need to iterate over the fields of a class. Right now there's one macro ready to go: JsonEncodable. As the name suggests, JsonEncodable generates the tedious fromJson and toJson methods for you.
Macros don't require anything extra to run, just add the appropriate annotation that you need to your code and observe as the boilerplate code is generated in real time! With them, code is not written to the disk, so say goodbye to the part keyword. Macros directly augment the existing class.
Macros, as a static metaprogramming tool, provide an alternative to runtime code generation solutions (such as build_runner, json_serializable). Macros eliminate the need for a secondary tool, being integrated into Dart language, executing automatically in the background by Dart tools with every keystroke of your keyboard
Let's compare 3 different code snippets, first without any code gen, second with Freezed and then third with macros
class Facility {
final int id;
final String name;
final String description;
final Address address;
final double lat;
final double lon;
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'address': address.toJson(),
'lat': lat,
'lon': lon,
};
factory Facility.fromJson(Map<String, dynamic> json) {
return Facility(
id: json['id'],
name: json['name'],
description: json['description'],
address: Address.fromJson(json['address']),
lat: json['lat'],
lon: json['lon'],
);
}
}
Just typing this part out took a lot longer than it should for such a generic boilerplate. Obviously this is not even the whole class, it's still missing the hashCode, == operator and possibly the toString overrides, and then do this for every (or at least most) model classes, and it turns out you've spent a lot of time on boilerplate code that is a lot of the time - error prone.
part 'facility.freezed.dart';
part 'facility.g.dart';
@Freezed()
class Facility with _$Facility {
const Factory Facility({
required int id,
required String name,
required String description,
required Address address,
required double lat,
required double lon,
}) = _Facility;
const Facility._();
factory Facility.fromJson(Map<String,dynamic> json) => _$FacilityFromJson(json);
}
This looks a lot less error prone than manually writing the functions, but now you've got 2 new files that you need to work around, push to the project github or set up .gitignore. A lot less work than with manual writing, but still there are some parts of these files that could be considered as boilerplate.
import 'package:json/json.dart';
@JsonEncodable()
class Facility {
final int id;
final String name;
final String description;
final Address address;
final double lat;
final double lon;
}
And that's it! No other code needed to make this work. While you are typing the last semicolon the code is already generated and ready to be used. After writing this class we can go to some other file, and be able to do things like this:
void main() {
var totallyValidFacilityJson = {...};
// Use the generated members:
var facility = Facility.fromJson(userJson);
print(facility);
print(facility.toJson());
}
Macros help you focus on the important stuff—the core logic of your product—and free you from the time-consuming, boring tasks that are essential but often get in the way.

Set up the environment:
analyzer:
enable-experiment:
- macros
import 'package:json/json.dart';dart run --enable-experiment=macros bin/my_app.dartYour first custom macro should have two key segments, the macro keyword and an interface which determines for which interface the macro is determined. You can find the list of existing interfaces here.
For example, a macro that is applicable to enums, and adds new declarations to the enum, would implement the EnumDefinitionMacro interface:
macro class MyFirstMacro implements EnumDefinitionMacro {
const MyFirstMacro();
// ...
}
With your custom macro created, you are ready to add it to a declaration, like below:
@MyFirstMacro()
class MacroExperimenting {}
The Important thing to keep in mind is that at a high-level macros are using builders methods to combine properties of your declaration with identifiers on those properties, gathered through introspection of the codebase
The release of macros to stable is, as of the time of writing, unknown. A specific date has not yet been set due to the fact that macros work by deeply introspecting the codebase in which they're applied. The general target for the release of JsonEncodable macro is projected to be until the end of 2024, and the stable release of the full functionality, including creating custom macros, for early-to-mid 2025.

Augmentation via the 'augment' keyword is the next step of evolution of the part keyword and part files, just without the extra syntax that makes your classes less clean. Augmentation allows defining class functions outside of the main class file.
For example we have this main class Person:
class Person {
Person(this.name);
final String name;
}
Now with the base class set, we can create our own json serialisation methods in a different part of the project, like this:
library augment 'models.dart';
augment class Person {
Person.from(Map<String, dynamic> data) : name = data['name'];
dynamic toJson() => {
'name': name,
};
}
And after adding the import below, you will be good to go!
import augment 'models_json.dart';
To improve the developer experience, of course! There is nothing wrong with these packages, they do the job perfectly, but with macros we make code cleaner by removing the _$ syntax needed for the packages mentioned above, as well as reduce the amount of workload on the developer to manage gitignored files or on peer review scroll endlessly over generated files to get to the crux of the pull request.
For more on state management approaches and reducing boilerplate, see our guide to the best Flutter state management libraries.
Discover our portfolio of successful projects, from renowned brands like bodybuilding.com to cutting-edge retail platforms like Leslie, and see how Foresight Mobile can turn your vision into reality. For the latest tech updates and more on flutter macros check out the Foresight Mobile blog. If you have any questions about our Flutter app development services, feel free to get in touch with us today.