name: "applying-effective-dart" description: "Dart 3 modern features (through 3.10) and Effective Dart best practices including records, patterns, sealed classes, dot shorthands, null-aware collection elements, and wildcard variables. Use when adopting Dart 3.7+ syntax, using dot shorthands for enums or static members, using null-aware elements in lists or maps, using wildcard _ parameters, improving code quality, or reviewing Dart code conventions." metadata: last_modified: "2026-04-01 17:00:00 (GMT+8)"
Dart 3 Latest Features and Effective Dart Best Practices Guide
Goal
Covers Dart 3 features (through 3.10) and Effective Dart practices for writing safe, maintainable code.
Instructions
1. Dart 3 Features
1.1 Records
Records aggregate multiple typed values without requiring a dedicated class.
- Best Practices:
- Multiple Return Values: Prefer Records over throwaway wrapper classes.
- Named Fields: Use named fields when returning more than two values or when semantics are ambiguous.
// ✅ Recommended Usage ({double lat, double lon}) getLocation(String city) { return (lat: 25.0330, lon: 121.5654); }
1.2 Patterns and Destructuring
Patterns match and destructure data from Records, Lists, Maps, and custom objects.
- Best Practices:
- Destructuring on declaration: Extract values from Records or Lists directly at assignment.
- Replace complex if-statements: Use
if-caseto validate shape and extract variables simultaneously.
// ✅ Recommended Usage final json = {'user': ['Zack', 25]}; if (json case {'user': [String name, int age]}) { print('User $name is $age years old.'); }
1.3 switch Expressions and Exhaustive Checking
switch expressions return values and provide compile-time exhaustiveness for sealed classes and enums.
- Best Practices:
- Use as expression: Assign diverging values concisely with switch expressions.
- Omit
default: On sealed/enum types, omitdefaultso the compiler catches unhandled cases when new variants are added.
1.4 Class Modifiers
Dart 3 class modifiers control inheritance and implementation boundaries.
- Best Practices:
sealed: For Algebraic Data Types (ADTs). Enables exhaustive switch checking. Ideal for State or Result/Error types.interface: Allowsimplementsonly; preventsextends.final: Prevents bothextendsandimplementsfrom external libraries.
1.5 Wildcard Variables — Dart 3.7
Local variables and parameters named _ are now non-binding — they can be declared multiple times without name collision, and their value cannot be read.
// Multiple _ params in constructors — no collision
Foo(_, this._, super._) {}
// Discard unused callback args cleanly
list.where((_) => true);
// Multiple discards in the same scope
var _ = expensiveCall1();
var _ = expensiveCall2(); // OK — no shadowing error
Top-level names, field names, and class names called _ are unchanged — this only applies to local variables and parameters.
1.6 Null-Aware Collection Elements — Dart 3.8
Prefix a nullable expression with ? inside a collection literal to omit it when null. Works in lists, sets, and maps.
String? lunch = isTuesday ? 'tacos' : null;
int? count = hasItems ? items.length : null;
// ❌ Before 3.8
var old = [if (lunch != null) lunch, if (count != null) count];
// ✅ Dart 3.8+
var menu = [?lunch, ?count]; // nulls are silently excluded
// Works in maps too
final headers = <String, String>{
'Content-Type': 'application/json',
if (token != null) 'Authorization': 'Bearer $token', // old style
?'X-Custom': customHeader, // ✅ null-aware
};
1.7 Dot Shorthands — Dart 3.10
Omit the type name when accessing a static member or constructor in a context where the expected type is already known.
// ❌ Before 3.10
Color color = Color.blue;
Widget w = Padding(padding: EdgeInsets.all(16), child: ...);
crossAxisAlignment: CrossAxisAlignment.start,
// ✅ Dart 3.10+
Color color = .blue;
Widget w = Padding(padding: .all(16), child: ...); // EdgeInsets inferred
crossAxisAlignment: .start,
mainAxisSize: .min,
// Works in switch cases
switch (color) {
case .blue: print('blue');
case .red: print('red');
}
Dot shorthands work for: enum values, static fields, static methods, and named constructors — anywhere the context type is known.
2. Effective Dart: Style
Consistent style enables collaboration.
- Formatting: Use
dart formatand enablecoreorrecommendedlints inanalysis_options.yaml. - Naming Conventions:
- Classes, Enums, Typedefs, Type parameters:
UpperCamelCase - Libraries, Packages, Directories, Files:
lowercase_with_underscores - Variables, Parameters, Functions, Methods:
lowerCamelCase - Constants:
lowerCamelCase(e.g.,const defaultTimeout = 1000;) — not SCREAMING_CAPS.
- Classes, Enums, Typedefs, Type parameters:
3. Effective Dart: Usage
3.1 Variables and Types
- Type Inference: Use
var/finalfor local variables; omit type annotations where the compiler infers correctly. - Collections: Initialize with literals (
var list = [];). Use spread operators and collectionif/forinstead of.add()calls. late: Use only when initialization must be deferred. Never access before assignment.
3.2 Functions and Async
- Arrow Functions: Use
=>for single-expression functions. - Named Parameters: Prefer named parameters for multiple optional arguments; apply
requiredas needed. - Async: Prefer
async/awaitover.then()/.catchError()chains.
3.3 Null Safety
- Minimize nullables: Design variables as non-nullable where possible.
- Avoid
!: Use type promotion (if (value != null)) or pattern matching instead of forced unwrapping. - Null-aware collection elements: Prefer
[?nullable]over[if (x != null) x]for cleaner collection literals (Dart 3.8+).
3.4 Async Annotations — Dart 3.9
@awaitNotRequired: Annotate aFuture-returning method to suppressunawaited_futureslint at the call site when intentionally fire-and-forget.
4. Effective Dart: API Design
- Minimal exposure: Only expose members that require external access. Prefix private members with
_. - Getters/Setters: Use
getfor property-like reads with no side effects or expensive computation. Don't add setters for every property — preferfinalconstructor initialization. - Constructors: Use named constructors (e.g.,
User.fromJson) and redirecting constructors for semantics. Provideconstconstructors wherever possible.
5. Recommended Lints (Dart 3.9+)
| Lint | Purpose |
|---|---|
switch_on_type | Prefer switch over is-chain if for type checks |
unnecessary_unawaited | Flag unawaited() calls where the future is already non-async |
Constraints
- Use
Recordsfor composite returns instead of one-off data classes. - Omit type annotations where the compiler can infer the type safely.
- Use dot shorthands (
.value) when the context type is unambiguous — reduces enum and static verbosity. - Use
?elementin collection literals instead ofif (x != null) x— cleaner and more idiomatic (Dart 3.8+).