dart-guide

Applies to: Dart 3.0+, Sound Null Safety, Flutter, Server-side Dart

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dart-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-dart-guide

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 final locals, const constructors, 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.x opt-outs)

  • Manage dependencies with pubspec.yaml ; run dart pub get after changes

  • Pin dependency versions with caret syntax (^1.2.0 ) for libraries, exact for apps

  • Run dart pub outdated periodically 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 analyze with strict mode before committing

  • Libraries/packages: lowercase_with_underscores (my_package )

  • Classes/enums/typedefs: UpperCamelCase (UserProfile )

  • Functions/variables/parameters: lowerCamelCase (fetchUser )

  • Constants: lowerCamelCase (defaultTimeout , not DEFAULT_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 required keyword for non-optional named parameters

  • Use late only 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 await Futures or return them; never fire-and-forget without error handling

  • Use async* and yield for Stream generators

  • Set timeouts on all external calls with .timeout(Duration(...))

  • Catch specific exceptions, not bare catch (e)

  • Use Completer only 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 final for all local variables that are not reassigned

  • Use const constructors for compile-time constant objects (widgets, values)

  • Prefer UnmodifiableListView or immutable collections from package:collection

  • Use records for lightweight immutable data groupings

  • Mark classes @immutable when 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 from lib/my_package.dart

  • test/ mirrors lib/src/ directory structure

  • bin/ 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.dart in test/ (mirror lib/src/ structure)

  • Test functions: test('description of behavior', () { ... })

  • Group related tests: group('ClassName', () { ... })

  • Use package:test for pure Dart, package:flutter_test for widgets

  • Use package:mockito with @GenerateMocks for 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';

@GenerateMocks([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&#x3C;Success&#x3C;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&#x3C;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

  • Effective Dart

  • Dart Language Tour

  • Dart 3 Records and Patterns

  • Dart Null Safety

  • Dart Concurrency (Isolates)

  • dart_test package

  • mockito for Dart

  • Dart Lints

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review