On this page · 11 sections
- The three features at a glance
- Primary constructors: the header syntax
- Private named parameters
- Dot shorthands
- Enabling primary constructors in a Flutter 3.44 project
- Old syntax to new syntax: the migration map
- Should you migrate an existing codebase?
- India-specific considerations
- FAQ
- How eCorpIT can help
- References
Summary. Dart 3.12, released with Flutter 3.44 at Google I/O 2026, adds three ways to delete constructor boilerplate: primary constructors (an experimental preview announced on the Dart blog on May 20, 2026), private named parameters, and the now-mature dot shorthand syntax that first landed in Dart 3.10. Primary constructors collapse a five-line Point class into a single line. Private named parameters let a constructor assign a private field such as _db with no initializer list. Andrea Bizzotto, the Flutter educator behind Code With Andrea, migrated a 300,000-line codebase to the new syntax in under one second using tooling he built. This guide shows the exact syntax for each feature, how to switch it on in a Flutter 3.44 project, and when the savings are worth the experimental risk.
Dart classes have carried the same shape since version 1.0: declare the fields, then repeat their names in a constructor to assign them. For a data model with eight fields, that is the field list plus eight this.field assignments, written twice. The Dart 3.12 release targets exactly that repetition. It arrived on May 20, 2026, one of two language releases the Dart team shipped at Google I/O 2026, and it pairs with Flutter 3.44.
Two of the three features here are genuinely new in Dart 3.12: primary constructors and private named parameters. The third, dot shorthands, first appeared in Dart 3.10 and matured through Dart 3.11 and 3.12. Grouped together, they attack the same problem from different angles: less repeated typing at the point where a class is declared. Here is what each one does, in the order you will reach for them.
The three features at a glance
| Feature | Dart version | What it removes |
|---|---|---|
| Primary constructors | 3.12 (experimental) | Repeated field declarations and the main constructor body |
| Private named parameters | 3.12 (stable) | The initializer list needed to assign private fields |
| Dot shorthands | 3.10, matured by 3.12 | The redundant type name before an enum value or static member |
const primary constructors |
3.12 (experimental) | Boilerplate on immutable classes and Flutter widgets |
Constructor shorthand (new) |
3.12 (experimental) | Repeated long class names in secondary constructors |
Primary constructors carry the most weight, so start there.
Primary constructors: the header syntax
A primary constructor declares a class's fields and its main constructor in the class header, not the body. Take the canonical example. Before Dart 3.12 you would write:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
The field names appear twice, and the constructor exists only to assign them. With a primary constructor, the same class is one line:
class Point(final int x, final int y);
The call site does not change. You still write final point = Point(1, 2);, as Andrea Bizzotto shows in his walkthrough. The final modifier on each parameter induces a matching field, so x and y are still final int fields on the class.
Primary constructors are not limited to toy examples. Named parameters, required, and default values all carry over. A currency model like this:
class CurrencyRates {
CurrencyRates({
required this.amount,
this.base = Currency.usd,
required this.date,
required this.rates,
});
final double amount;
final Currency base;
final DateTime date;
final Map<Currency, double> rates;
}
becomes a header with the same public shape:
class CurrencyRates({
required final double amount,
final Currency base = Currency.usd,
required final DateTime date,
required final Map<Currency, double> rates,
});
The default value for base and the required markers survive the move. Any extra factory or named constructors stay in the body.
Enhanced enums and sealed hierarchies
Enhanced enums repeat a constructor shape across every value, so they gain a lot from this syntax. A chart time-range enum that used to declare a const constructor plus four fields can pull those into the header:
enum ChartTimeRange(final String label, final int days, final int months, final int years) {
oneWeek('1W', 7, 0, 0),
oneMonth('1M', 0, 1, 0),
threeMonths('3M', 0, 3, 0),
oneYear('1Y', 0, 0, 1),
fiveYears('5Y', 0, 0, 5),
all('ALL', 0, 0, 0),
;
}
The enum values are untouched; only the separate constructor and field block disappear. Sealed hierarchies get a smaller but pleasant win. An empty class body {} collapses to a semicolon:
sealed class DatabaseIOResult;
class DatabaseIOSuccess({final String? path}) extends DatabaseIOResult;
class DatabaseIOCancelled extends DatabaseIOResult;
class DatabaseIOError(final String message) extends DatabaseIOResult;
const and Flutter widgets
The detail most people miss is const. With a primary constructor, the const keyword goes between class and the class name, which reads oddly at first:
class const Insets(final double value);
const small = Insets(8);
Keeping const is not optional cosmetics. It affects compile-time constant construction, canonicalisation, required const contexts such as annotations, and Flutter widget allocation. A migration that silently drops const breaks those call sites, so preserve it.
That is why the feature matters for Flutter specifically. Widget classes are immutable and repetitive. A StatelessWidget like PrimaryButton with a const constructor, two fields, and a build method can shed its constructor block:
class const PrimaryButton({
super.key,
required final String label,
required final VoidCallback? onPressed,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}
}
The constructor stays const, super.key is preserved, and the widget behaves identically. For a codebase with hundreds of small widgets, that is a real reduction in lines. If you are already tracking the framework's direction, this fits the same theme as Flutter's push toward Gemini-assisted development and the Dart MCP server.
Private named parameters
The second feature is smaller but removes a daily annoyance. Before Dart 3.12, a named parameter could not begin with an show, so you could not assign a private field directly through a named argument. Initializing a private field meant an explicit initializer list:
class SnapshotRepository {
SnapshotRepository(AppDatabase db) : _db = db;
final AppDatabase _db;
}
Dart 3.12 allows private named parameters as initializing formals. You write this._db and the compiler does the rest:
class SnapshotRepository {
SnapshotRepository(this._db);
final AppDatabase _db;
}
The parameter name exposed to callers is the public version with the show stripped, so the public API does not change. As the team at Start Debugging documents, the rule applies only to initializing formals. A regular named parameter still cannot start with an show, because that would leak a private name into the public signature. The show also has to map to a legal public identifier: this._ and this._2x are rejected, since stripping the show leaves an empty string or an illegal name.
Primary constructors take this one step further by declaring the private field in the header:
class SnapshotRepository(final AppDatabase _db) {
// ...
}
Either form instantiates the same way: final repo = SnapshotRepository(AppDatabase());.
Dot shorthands
The third feature predates 3.12 but is worth including because it is now stable and it cuts a different kind of repetition. Dot shorthands let you drop the type name before an enum value, static member, or constructor when the compiler can infer the type from context. It landed in Dart 3.10, gained analyzer and editor support in Dart 3.11, and is dependable by the Dart 3.12 toolchain.
The core idea is context typing. If a Status is expected, .running resolves to Status.running:
Status currentStatus = .running;
It works in equality checks, where the static type of the left side supplies the context:
if (myColor == .green) {
// .green is inferred as Color.green
}
It also covers static methods and const contexts:
int port = .parse('8080');
Dot shorthands shine in switch statements and assignments where the enum type is obvious. They do not save whole lines the way primary constructors do, but across a large file the removed type names add up, and the code reads cleaner. For a fuller picture of what shipped this cycle, see everything Flutter and Dart announced at Google I/O 2026.
Enabling primary constructors in a Flutter 3.44 project
Private named parameters and dot shorthands are on by default in Dart 3.12. Primary constructors are experimental, so you have to opt in. The steps below follow Andrea Bizzotto's setup guide.
First, set the SDK constraint in pubspec.yaml:
environment:
sdk: ^3.12.0
Next, enable the analyzer experiment so the analyzer stops complaining:
analyzer:
enable-experiment:
- primary-constructors
If you are on a Flutter project, run Flutter 3.44.0 or higher. Commands that compile or format code need the flag too:
flutter run --enable-experiment=primary-constructors
flutter test --enable-experiment=primary-constructors
dart format --enable-experiment=primary-constructors <changed-dart-files>
If you launch from VS Code or Cursor, add the argument to .vscode/launch.json so debug runs pick it up:
{
"version": "0.2.0",
"configurations": [
{
"name": "app",
"request": "launch",
"type": "dart",
"args": ["--enable-experiment=primary-constructors"]
}
]
}
Run flutter analyze (or dart analyze for a pure package) before touching any code. If the project already has analyzer issues, resolve those first so you do not mix them with migration noise.
Old syntax to new syntax: the migration map
Not every constructor moves into the header. Redirecting and secondary constructors stay in the body and use a new shorthand instead. The feature specification lists the full mapping. The common cases:
| Original Dart syntax | Abbreviated syntax | When it applies |
|---|---|---|
LongClassName() {} |
new() {} |
Default constructor kept in the body |
LongClassName.name() {} |
new name() {} |
Named constructor in the body |
const LongClassName(); |
const new(); |
Const default constructor |
LongClassName(): this.other(); |
new(): this.other(); |
Redirecting constructor |
factory LongClassName() = D; |
factory() = D; |
Redirecting factory |
Where multiple named constructors exist, only one can become the primary constructor; the rest migrate to the new shorthand. Field initializers still work, because primary constructor parameters are in scope inside the class body.
Should you migrate an existing codebase?
Here the honest answer is: not by hand, and not everywhere. Primary constructors are experimental as of Dart 3.12, there is no dart fix for them, and the old syntax stays valid. A safe migration has to preserve constructor shapes, const behaviour, comments, generated-file boundaries, and public API compatibility. Doing that manually across a large project is slow and easy to get wrong.
Automated tooling changes the calculation. Andrea Bizzotto built a migration CLI and tested it on a fork of the Flutter Gallery project that runs to more than 300,000 lines of code. It finished the migration in under one second while preserving the details above. As he puts it: "Primary constructors are a nice quality-of-life improvement for Dart. They can remove a lot of constructor boilerplate from classes, enums, sealed hierarchies, and Flutter widgets."
That framing is the right one. This is a quality-of-life change, not a rewrite you need to rush. The practical options break down like this:
| Approach | Speed | Risk |
|---|---|---|
| Do nothing | Instant | None; old syntax stays valid |
| Migrate by hand | Slow | High on large codebases |
| Ad hoc coding-agent script | Variable | Token-heavy, hard to verify at scale |
| Deterministic migration CLI | Seconds | Low when it preserves const and API shape |
For most teams, the sensible path is to write new code with the features you get for free, private named parameters and dot shorthands, hold primary constructors for greenfield files or a deliberate tooling-assisted pass, and keep the experiment flag out of production release builds until the feature stabilises. The same caution applies to any experimental Flutter change, such as Impeller becoming the mandatory renderer: adopt on your schedule, not the changelog's.
India-specific considerations
For teams in India building client work under fixed-scope contracts, the experimental status matters more than the syntax. A feature behind an experiment flag can change before it stabilises, and a client codebase that depends on it may need rework. The pragmatic split is to use the two stable Dart 3.12 features in delivery code now, and reserve primary constructors for internal tooling, prototypes, or projects where your team controls the upgrade cadence.
There is also a hiring and review angle. Primary constructor syntax reads differently, especially the class const Name(...) form. If you run a multi-developer team, agree on a convention and update your review checklist before the syntax spreads through a repository, so pull requests stay readable for everyone.
FAQ
How eCorpIT can help
eCorpIT builds and maintains production Flutter apps for clients in India and abroad, and we track each Dart and Flutter release so upgrades do not surprise our clients. If your team wants a Dart 3.12 readiness review, a safe primary-constructors migration plan, or a second opinion on which features belong in delivery code versus internal tooling, our senior engineering team can help. Reach us through the contact page to start a conversation.
References
- Dart team, Announcing Dart 3.12, dart.dev, May 20, 2026.
- Dart documentation, Primary constructors, dart.dev.
- Dart documentation, Constructors: private named parameters, dart.dev.
- Dart documentation, Dot shorthands, dart.dev.
- Andrea Bizzotto, Dart Primary Constructors are Cool! Here's What They Look Like, Code With Andrea, June 12, 2026.
- Andrea Bizzotto, How to Safely Migrate Dart Code to Primary Constructors, Code With Andrea.
- Start Debugging, Dart 3.12 Drops the Initializer List for Private Fields, May 2026.
- Flutter documentation, Flutter 3.44.0 release notes, docs.flutter.dev.
- Flutter documentation, What's new in the docs, docs.flutter.dev.
- Dart language team, Primary constructors feature specification, GitHub.
- Dart documentation, What's new, dart.dev.
- Yuri Novicow, New in Dart 3.12, Medium, June 2026.
_Last updated: July 7, 2026._