Chuyển đến nội dung chính

Dart 3.13 primary constructors: less boilerplate, one real breaking change

Dart 3.13 ships alongside Flutter 3.47, and its headline feature is the one Dart developers have been asking for since the language got null safety: primary constructors are stable. Declare your fields and your constructor in the class header, and delete the three-line ceremony that every model class in your codebase currently repeats.

There is also a breaking change hiding in the release, and it is not the one most people expect.

The basic shape

The traditional form:

class Point {
  int x;
  int y;
  Point(this.x, this.y);
}

The primary constructor form:

class Point(var int x, var int y);

That is the entire class. Note the ordering — the modifier comes first, then the type, then the name: var int x, not int var x.

Declaring parameters are the actual feature

This is the part worth understanding properly, because it is where the design is subtler than “shorter syntax”.

A parameter prefixed with var or final is a declaring parameter: it induces an instance field. A parameter without either modifier behaves like an ordinary constructor argument and creates no field at all.

// Creates fields x and y
class Point(var int x, var int y);

// No field created — `name` is just an argument
class User(String name);

That distinction gives you a validation-only constructor for free, without the usual trick of a private field you never read.

For immutable models — which is most of them in a Flutter codebase — use final:

class ConstPoint(final int x, final int y);

Named parameters, and the underscore trick

Named parameters work as you would expect, with one nice detail: a private named parameter automatically exposes a public name to callers.

class User({required var String _name});
// Called as: User(name: 'John Doe')

The field is _name, private to the library. The argument label is name, public. You get encapsulation without writing a constructor that maps one to the other.

Named and private primary constructors

Append a dot and a name after the class identifier:

class Point.custom(var int x, var int y);

// Private — restricts direct instantiation from outside the library
class Point._(var int x, var int y);

The private form is the sealed-ish pattern many packages implement by hand today.

Const primary constructors

Put const before the parameter list:

class ConstPoint const (final int x, final int y) {
  final int z;
  this : z = x + y;
}

The constraints are the ones you would guess from const semantics:

  • no body block
  • every field must be final and definitely initialised
  • initialising expressions must be potentially constant

For Flutter this matters more than it looks. Const widget constructors are a real performance lever, and anything that makes them cheaper to write means more of them get written.

Adding a body without giving up the header

You are not choosing between a primary constructor and constructor logic. this : introduces an initializer list, a body, or both:

class Point(var int x, var int y) {
  this : assert(x >= 0 && y >= 0) {
    print('Point initialized at ($x, $y)');
  }
}

Super parameters forward cleanly:

class Person(final String name, final int age);

class Employee(super.name, super.age, final String role) extends Person;

The scoping rule that will trip you up

There are two scopes, and they resolve the same identifier differently:

ScopeWherex refers to
Primary initializer scopeField initializers, initializer listthe parameter
Primary parameter scopeConstructor bodythe field (for declaring parameters)

Concretely:

class ScopingDemo(var String x, String suffix) {
  final String field = x;  // 'x' is the parameter

  this : {
    x = x.toUpperCase();   // 'x' now refers to the field
    print('$x$suffix');    // 'suffix' is still the parameter
  }
}

Read that twice before you refactor a class with a non-trivial initializer list. It is consistent, but it is not obvious.

Restrictions

Compile-time constraints worth knowing before you start converting classes:

  • declaring parameters cannot be late or external
  • parameter names cannot collide with existing methods or fields
  • parameters are read-only inside the primary initializer scope
  • you cannot initialise a field both at its declaration and in the primary constructor
  • mixin classes may only declare trivial primary constructors — no parameters, no initializer list, no body
  • covariant only works with mutable (var) declaring parameters

The breaking change

Here is the part to plan for. final and var on regular function parameters are now reserved exclusively for primary constructor declaring parameters. Existing code that wrote void f(final int x) becomes invalid.

The lint story changed accordingly: parameter_assignments (Dart 3.13+) replaces the older avoid_final_parameters and var_with_no_type_annotation from 3.12 and earlier.

There is a second, quieter hazard: a method named factory without an explicit return type can now be misparsed as a factory constructor. If you have one, add the return type annotation.

What else is in 3.13

Beyond the language change:

  • dart2wasm deferred loading (preview)dart compile wasm -O2 --enable-deferred-loading, with meaningful initial-page-load improvements over dart2js for large apps
  • @RecordUse() in package:meta, enabling native libraries to be tree-shaken alongside Dart code
  • formatter refinements — a fix for misformatting around large collection literals, better method-chain split heuristics, and blank lines between import sections per Effective Dart
  • type promotion soundness fix for nested functions
  • faster dartdoc rendering on pub.dev via a two-level hash index

Adopting it without churn

  1. Upgrade and build. Fix any void f(final int x) parameters the analyzer now rejects.
  2. Add explicit return types to any method named factory.
  3. Enable parameter_assignments and remove avoid_final_parameters / var_with_no_type_annotation from analysis_options.yaml.
  4. Start with plain data classes — DTOs, value objects, freezed-adjacent models. Highest boilerplate reduction, lowest risk.
  5. Use the four IDE refactorings to move between primary and in-body constructors rather than editing by hand.
  6. Convert const widget constructors next, once you are comfortable with the const form’s constraints.
  7. Leave classes with complex initializer lists for last, and re-read the scoping table before you touch them.

The bottom line

Primary constructors are the rare language feature that is purely subtractive: same semantics, less typing, no new concepts once you understand declaring parameters. The var/final restriction on function parameters is a genuine breaking change, but a mechanical one your analyzer will find in a single build. Convert your data classes this week; leave the gnarly ones until you have internalised the two-scope rule.


Originally published on FlutterCook. Read the latest version there — that copy is the one kept up to date.

Nhận xét

Bài đăng phổ biến từ blog này

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

5 concepts every Flutter dev should know

  Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...

Announcing Flutter 2

  Phụ lục: Flutter on the web Flutter 2 on desktops, foldables, and embedded devices The growing Flutter ecosystem Dart: The secret sauce behind Flutter Flutter 2: Available now Hôm nay, chúng tôi sẽ công bố Flutter 2: một bản nâng cấp lớn cho Flutter cho phép các nhà phát triển tạo các ứng dụng đẹp, nhanh chóng và di động cho bất kỳ nền tảng nào. Với Flutter 2, bạn có thể sử dụng cùng một cơ sở mã để gửi các ứng dụng gốc cho năm hệ điều hành: IOS, Android, Windows, macOS và Linux; cũng như trải nghiệm web nhắm mục tiêu các trình duyệt như Chrome, Firefox, Safari hoặc Edge. Flutter thậm chí có thể được nhúng vào ô tô, TV và thiết bị gia dụng thông minh, mang đến trải nghiệm di động và lan tỏa nhất cho thế giới điện toán xung quanh. Mục tiêu của chúng tôi là thay đổi cơ bản cách các nhà phát triển nghĩ về việc xây dựng ứng dụng, bắt đầu không phải với nền tảng bạn đang nhắm mục tiêu mà là với trải nghiệm bạn muốn tạo. Flutter cho phép bạn tạo ra những trải nghiệm tuyệt đẹp trong đó ...