dart-guide
SKILL.md
Dart Guide
Applies to: Dart 3.0+, Sound Null Safety, Flutter, Server-side Dart
Core Principles
- Sound Null Safety: The type system guarantees non-nullable by default; nullable types are explicit
- Async by Design: Use Futures, Streams, and async/await for all I/O and event-driven logic
- Immutability First: Prefer
finallocals,constconstructors, and immutable data structures - Composition Over Inheritance: Use mixins, extension methods, and sealed class hierarchies
- Effective Dart: Follow official Effective Dart style for naming, documentation, and API design
Guardrails
Version & Dependencies
- Use Dart 3.0+ with sound null safety enabled (no
// @dart=2.xopt-outs) - Manage dependencies with
pubspec.yaml; rundart pub getafter changes - Pin dependency versions with caret syntax (
^1.2.0) for libraries, exact for apps - Run
dart pub outdatedperiodically to check for updates - Audit transitive dependencies with
dart pub deps
Code Style
- Run
dart format .before every commit (line length: 80 characters) - Run
dart analyzewith strict mode before committing - Libraries/packages:
lowercase_with_underscores(my_package) - Classes/enums/typedefs:
UpperCamelCase(UserProfile) - Functions/variables/parameters:
lowerCamelCase(fetchUser) - Constants:
lowerCamelCase(defaultTimeout, notDEFAULT_TIMEOUT) - Private members: prefix with
_(_internalState) - Follow Effective Dart guidelines
Null Safety
- Never use
!(null assertion) without a preceding null check or guarantee - Use
?for nullable types only at API boundaries (JSON, platform channels) - Prefer
??(if-null) and?.(null-aware access) over explicit null checks - Use
requiredkeyword for non-optional named parameters - Use
lateonly when initialization is guaranteed before access (avoid for laziness)
// BAD: crashes at runtime if null
final name = json['name'] as String;
// GOOD: explicit nullable handling
final name = json['name'] as String? ?? 'Unknown';
// GOOD: required parameter enforces non-null at call site
void createUser({required String name, required String email}) { ... }
// BAD: late used for laziness without guarantees
late final String config;
// GOOD: late with guaranteed initialization in constructor body
late final Database _db;
void init(Database db) => _db = db;
Async/Await
- Always
awaitFutures or return them; never fire-and-forget without error handling - Use
async*andyieldfor Stream generators - Set timeouts on all external calls with
.timeout(Duration(...)) - Catch specific exceptions, not bare
catch (e) - Use
Completeronly when bridging callback-based APIs to Futures
// BAD: fire-and-forget loses errors
void save() {
repository.save(data); // Future ignored
}
// GOOD: propagate or handle
Future<void> save() async {
await repository.save(data);
}
// GOOD: timeout on external calls
final response = await http.get(uri).timeout(
const Duration(seconds: 10),
onTimeout: () => throw TimeoutException('Request timed out'),
);
Immutability
- Use
finalfor all local variables that are not reassigned - Use
constconstructors for compile-time constant objects (widgets, values) - Prefer
UnmodifiableListViewor immutable collections frompackage:collection - Use records for lightweight immutable data groupings
- Mark classes
@immutablewhen all fields are final
Project Structure
myproject/
├── lib/
│ ├── src/ # Private implementation
│ │ ├── models/ # Data classes, records, sealed types
│ │ ├── services/ # Business logic, use cases
│ │ ├── repositories/ # Data access layer
│ │ └── utils/ # Shared utilities, extensions
│ └── my_package.dart # Public barrel export file
├── bin/ # CLI entry points (for command-line apps)
│ └── main.dart
├── test/ # Tests (mirrors lib/src/ structure)
│ ├── models/
│ ├── services/
│ └── repositories/
├── pubspec.yaml # Dependencies and metadata
├── analysis_options.yaml # Linter and analyzer configuration
└── README.md
lib/src/for private implementation; export public API fromlib/my_package.darttest/mirrorslib/src/directory structurebin/only for executable entry points- No business logic in
main.dart(delegate to services)
Key Patterns
Records and Pattern Matching (Dart 3)
// Records: lightweight immutable tuples with named fields
typedef UserSummary = ({String name, String email, int age});
UserSummary fetchSummary() => (name: 'Alice', email: 'a@b.com', age: 30);
// Destructuring with pattern matching
final (name: userName, email: _, age: userAge) = fetchSummary();
// Switch expressions with patterns
String classify(Object value) => switch (value) {
int n when n < 0 => 'negative',
int n when n == 0 => 'zero',
int() => 'positive',
String s => 'string: $s',
(int x, int y) => 'point ($x, $y)',
_ => 'unknown',
};
Sealed Classes and Exhaustive Matching
sealed class Result<T> {
const Result();
}
final class Success<T> extends Result<T> {
final T value;
const Success(this.value);
}
final class Failure<T> extends Result<T> {
final String message;
final Object? error;
const Failure(this.message, [this.error]);
}
// Exhaustive switch: compiler enforces all subtypes handled
String describe<T>(Result<T> result) => switch (result) {
Success(:final value) => 'Success: $value',
Failure(:final message) => 'Failed: $message',
};
Extension Methods and Types
// Extension methods: add functionality to existing types
extension StringValidation on String {
bool get isValidEmail => RegExp(r'^[\w-.]+@[\w-]+\.\w+$').hasMatch(this);
String truncate(int maxLength) =>
length <= maxLength ? this : '${substring(0, maxLength)}...';
}
// Extension types (Dart 3.3): zero-cost type wrappers
extension type UserId(String value) {
UserId.generate() : value = Uuid().v4();
bool get isValid => value.isNotEmpty;
}
// Usage: compile-time type safety, zero runtime overhead
void fetchUser(UserId id) { ... }
fetchUser(UserId('abc-123'));
// fetchUser('raw-string'); // Compile error
Late Keyword (Correct Usage)
class ApiClient {
// GOOD: late for dependency injection with guaranteed init
late final HttpClient _client;
void configure(HttpClient client) {
_client = client;
}
// GOOD: late final for expensive one-time computation
late final Map<String, dynamic> _config = _loadConfig();
Map<String, dynamic> _loadConfig() {
// expensive operation, runs once on first access
return jsonDecode(File('config.json').readAsStringSync());
}
}
Stream Patterns
// async* generator for producing streams
Stream<int> countDown(int from) async* {
for (var i = from; i >= 0; i--) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
// Stream transformations
final results = inputStream
.where((event) => event.isValid)
.map((event) => event.transform())
.handleError((error) => logger.warning('Stream error: $error'))
.distinct();
Isolates for Heavy Computation
import 'dart:isolate';
// Simple one-shot isolate computation
Future<List<int>> computePrimes(int limit) async {
return await Isolate.run(() => _sieveOfEratosthenes(limit));
}
List<int> _sieveOfEratosthenes(int limit) {
final sieve = List.filled(limit + 1, true);
sieve[0] = sieve[1] = false;
for (var i = 2; i * i <= limit; i++) {
if (sieve[i]) {
for (var j = i * i; j <= limit; j += i) {
sieve[j] = false;
}
}
}
return [for (var i = 2; i <= limit; i++) if (sieve[i]) i];
}
Testing
Standards
- Test files:
*_test.dartintest/(mirrorlib/src/structure) - Test functions:
test('description of behavior', () { ... }) - Group related tests:
group('ClassName', () { ... }) - Use
package:testfor pure Dart,package:flutter_testfor widgets - Use
package:mockitowith@GenerateMocksfor type-safe mocking - Coverage target: >80% for business logic, >60% overall
- Test names describe behavior:
'returns empty list when no users exist'
Unit Test Pattern
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
([UserRepository])
import 'user_service_test.mocks.dart';
void main() {
late MockUserRepository mockRepo;
late UserService service;
setUp(() {
mockRepo = MockUserRepository();
service = UserService(mockRepo);
});
group('UserService', () {
group('getUser', () {
test('returns user when found', () async {
final expected = User(id: '1', name: 'Alice');
when(mockRepo.findById('1')).thenAnswer((_) async => expected);
final result = await service.getUser('1');
expect(result, isA<Success<User>>());
expect((result as Success).value, equals(expected));
verify(mockRepo.findById('1')).called(1);
});
test('returns failure when user not found', () async {
when(mockRepo.findById('99')).thenAnswer((_) async => null);
final result = await service.getUser('99');
expect(result, isA<Failure>());
expect((result as Failure).message, contains('not found'));
});
});
});
}
Async and Stream Testing
test('stream emits values in order', () {
expect(
countDown(3),
emitsInOrder([3, 2, 1, 0, emitsDone]),
);
});
test('completes within timeout', () async {
final result = await fetchData().timeout(const Duration(seconds: 5));
expect(result, isNotNull);
});
Tooling
Essential Commands
dart format . # Format all Dart files
dart analyze # Static analysis (lint + type checks)
dart test # Run all tests
dart test --coverage=coverage # Run tests with coverage
dart run bin/main.dart # Run CLI application
dart compile exe bin/main.dart # AOT compile to native executable
dart pub get # Install dependencies
dart pub outdated # Check for dependency updates
dart pub deps # Show dependency tree
dart fix --apply # Auto-apply suggested fixes
Analysis Options Configuration
# analysis_options.yaml
include: package:lints/recommended.yaml
analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
missing_return: error
dead_code: warning
linter:
rules:
- prefer_final_locals
- prefer_const_constructors
- prefer_const_declarations
- avoid_dynamic_calls
- unawaited_futures
- always_declare_return_types
- annotate_overrides
- avoid_print
- prefer_single_quotes
- require_trailing_commas
- unnecessary_lambdas
- prefer_expression_function_bodies
Pub Configuration
# pubspec.yaml
name: my_package
description: A concise description of the package.
version: 1.0.0
environment:
sdk: ^3.0.0
dependencies:
http: ^1.2.0
json_annotation: ^4.8.0
dev_dependencies:
test: ^1.25.0
mockito: ^5.4.0
build_runner: ^2.4.0
json_serializable: ^6.7.0
lints: ^4.0.0
coverage: ^1.7.0
References
For detailed patterns and examples, see:
- references/patterns.md -- Stream patterns, Isolate communication, sealed class hierarchies
External References
Weekly Installs
5
Repository
ar4mirez/samuelGitHub Stars
3
First Seen
13 days ago
Security Audits
Installed on
opencode5
gemini-cli5
codebuddy5
github-copilot5
codex5
kimi-cli5