/dart-flutter-patterns
Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture.
$ npx -y skills add affaan-m/ECC --skill dart-flutter-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/dart-flutter-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture.
SKILL.md
dart-flutter-patterns.SKILL.mdname: dart-flutter-patterns
description: Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture.
metadata:
origin: ECC
Dart/Flutter Patterns
When to Use
Use this skill when:
- Starting a new Flutter feature and need idiomatic patterns for state management, navigation, or data access
- Reviewing or writing Dart code and need guidance on null safety, sealed types, or async composition
- Setting up a new Flutter project and choosing between BLoC, Riverpod, or Provider
- Implementing secure HTTP clients, WebView integration, or local storage
- Writing tests for Flutter widgets, Cubits, or Riverpod providers
- Wiring up GoRouter with authentication guards
How It Works
This skill provides copy-paste-ready Dart/Flutter code patterns organized by concern: 1. **Null safety** — avoid `!`, prefer `?.`/`??`/pattern matching 2. **Immutable state** — sealed classes, `freezed`, `copyWith` 3. **Async composition** — concurrent `Future.wait`, safe `BuildContext` after `await` 4. **Widget architecture** — extract to classes (not methods), `const` propagation, scoped rebuilds 5. **State management** — BLoC/Cubit events, Riverpod notifiers and derived providers 6. **Navigation** — GoRouter with reactive auth guards via `refreshListenable` 7. **Networking** — Dio with interceptors, token refresh with one-time retry guard 8. **Error handling** — global capture, `ErrorWidget.builder`, crashlytics wiring 9. **Testing** — unit (BLoC test), widget (ProviderScope overrides), fakes over mocks
Examples
// Sealed state — prevents impossible states
sealed class AsyncState<T> {}
final class Loading<T> extends AsyncState<T> {}
final class Success<T> extends AsyncState<T> { final T data; const Success(this.data); }
final class Failure<T> extends AsyncState<T> { final Object error; const Failure(this.error); }
// GoRouter with reactive auth redirect
final router = GoRouter(
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final authed = context.read<AuthCubit>().state is AuthAuthenticated;
if (!authed && !state.matchedLocation.startsWith('/login')) return '/login';
return null;
},
routes: [...],
);
// Riverpod derived provider with safe firstWhereOrNull
@riverpod
double cartTotal(Ref ref) {
final cart = ref.watch(cartNotifierProvider);
final products = ref.watch(productsProvider).valueOrNull ?? [];
return cart.fold(0.0, (total, item) {
final product = products.firstWhereOrNull((p) => p.id == item.productId);
return total + (product?.price ?? 0) * item.quantity;
});
}---
Practical, production-ready patterns for Dart and Flutter applications. Library-agnostic where possible, with explicit coverage of the most common ecosystem packages.
---
1. Null Safety Fundamentals
Prefer Patterns Over Bang Operator
// BAD — crashes at runtime if null
final name = user!.name;
// GOOD — provide fallback
final name = user?.name ?? 'Unknown';
// GOOD — Dart 3 pattern matching (preferred for complex cases)
final display = switch (user) {
User(:final name, :final email) => '$name <$email>',
null => 'Guest',
};
// GOOD — guard early return
String getUserName(User? user) {
if (user == null) return 'Unknown';
return user.name; // promoted to non-null after check
}Avoid `late` Overuse
// BAD — defers null error to runtime
late String userId;
// GOOD — nullable with explicit initialization
String? userId;
// OK — use late only when initialization is guaranteed before first access
// (e.g., in initState() before any widget interaction)
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
}---
2. Immutable State
Sealed Classes for State Hierarchies
sealed class UserState {}
final class UserInitial extends UserState {}
final class UserLoading extends UserState {}
final class UserLoaded extends UserState {
const UserLoaded(this.user);
final User user;
}
final class UserError extends UserState {
const UserError(this.message);
final String message;
}
// Exhaustive switch — compiler enforces all branches
Widget buildFrom(UserState state) => switch (state) {
UserInitial() => const SizedBox.shrink(),
UserLoading() => const CircularProgressIndicator(),
UserLoaded(:final user) => UserCard(user: user),
UserError(:final message) => ErrorText(message),
};Freezed for Boilerplate-Free Immutability
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
@Default(false) bool isAdmin,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Usage
final user = User(id: '1', name: 'Alice', email: 'alice@example.com');
final updated = user.copyWith(name: 'Alice Smith'); // immutable update
final json = user.toJson();
final fromJson = User.fromJson(json);---
3. Async Composition
Structured Concurrency with Future.wait
Future<DashboardData> loadDashboard(UserRepository users, OrderRepository orders) async {
// Run concurrently — don't await sequentially
final (userList, orderList) = await (
users.getAll(),
orders.getRecent(),
).wait; // Dart 3 record destructuring + Future.wait extension
return DashboardData(users: userList, orders: orderList);
}Stream Patterns
// Repository exposes reactive streams for live data
Stream<List<Item>> watchCartItems() => _db
.watchTable('cart_items')
.map((rows) => rows.Read more
name: dart-flutter-patterns description: Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture. metadata: origin: ECC
Dart/Flutter Patterns
When to Use
Use this skill when:
- Starting a new Flutter feature and need idiomatic patterns for state management, navigation, or data access
- Reviewing or writing Dart code and need guidance on null safety, sealed types, or async composition
- Setting up a new Flutter project and choosing between BLoC, Riverpod, or Provider
- Implementing secure HTTP clients, WebView integration, or local storage
- Writing tests for Flutter widgets, Cubits, or Riverpod providers
- Wiring up GoRouter with authentication guards
How It Works
This skill provides copy-paste-ready Dart/Flutter code patterns organized by concern: 1. **Null safety** — avoid `!`, prefer `?.`/`??`/pattern matching 2. **Immutable state** — sealed classes, `freezed`, `copyWith` 3. **Async composition** — concurrent `Future.wait`, safe `BuildContext` after `await` 4. **Widget architecture** — extract to classes (not methods), `const` propagation, scoped rebuilds 5. **State management** — BLoC/Cubit events, Riverpod notifiers and derived providers 6. **Navigation** — GoRouter with reactive auth guards via `refreshListenable` 7. **Networking** — Dio with interceptors, token refresh with one-time retry guard 8. **Error handling** — global capture, `ErrorWidget.builder`, crashlytics wiring 9. **Testing** — unit (BLoC test), widget (ProviderScope overrides), fakes over mocks
Examples
// Sealed state — prevents impossible states
sealed class AsyncState<T> {}
final class Loading<T> extends AsyncState<T> {}
final class Success<T> extends AsyncState<T> { final T data; const Success(this.data); }
final class Failure<T> extends AsyncState<T> { final Object error; const Failure(this.error); }
// GoRouter with reactive auth redirect
final router = GoRouter(
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final authed = context.read<AuthCubit>().state is AuthAuthenticated;
if (!authed && !state.matchedLocation.startsWith('/login')) return '/login';
return null;
},
routes: [...],
);
// Riverpod derived provider with safe firstWhereOrNull
@riverpod
double cartTotal(Ref ref) {
final cart = ref.watch(cartNotifierProvider);
final products = ref.watch(productsProvider).valueOrNull ?? [];
return cart.fold(0.0, (total, item) {
final product = products.firstWhereOrNull((p) => p.id == item.productId);
return total + (product?.price ?? 0) * item.quantity;
});
}---
Practical, production-ready patterns for Dart and Flutter applications. Library-agnostic where possible, with explicit coverage of the most common ecosystem packages.
---
1. Null Safety Fundamentals
Prefer Patterns Over Bang Operator
// BAD — crashes at runtime if null
final name = user!.name;
// GOOD — provide fallback
final name = user?.name ?? 'Unknown';
// GOOD — Dart 3 pattern matching (preferred for complex cases)
final display = switch (user) {
User(:final name, :final email) => '$name <$email>',
null => 'Guest',
};
// GOOD — guard early return
String getUserName(User? user) {
if (user == null) return 'Unknown';
return user.name; // promoted to non-null after check
}Avoid `late` Overuse
// BAD — defers null error to runtime
late String userId;
// GOOD — nullable with explicit initialization
String? userId;
// OK — use late only when initialization is guaranteed before first access
// (e.g., in initState() before any widget interaction)
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
}---
2. Immutable State
Sealed Classes for State Hierarchies
sealed class UserState {}
final class UserInitial extends UserState {}
final class UserLoading extends UserState {}
final class UserLoaded extends UserState {
const UserLoaded(this.user);
final User user;
}
final class UserError extends UserState {
const UserError(this.message);
final String message;
}
// Exhaustive switch — compiler enforces all branches
Widget buildFrom(UserState state) => switch (state) {
UserInitial() => const SizedBox.shrink(),
UserLoading() => const CircularProgressIndicator(),
UserLoaded(:final user) => UserCard(user: user),
UserError(:final message) => ErrorText(message),
};Freezed for Boilerplate-Free Immutability
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
@Default(false) bool isAdmin,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Usage
final user = User(id: '1', name: 'Alice', email: 'alice@example.com');
final updated = user.copyWith(name: 'Alice Smith'); // immutable update
final json = user.toJson();
final fromJson = User.fromJson(json);---
3. Async Composition
Structured Concurrency with Future.wait
Future<DashboardData> loadDashboard(UserRepository users, OrderRepository orders) async {
// Run concurrently — don't await sequentially
final (userList, orderList) = await (
users.getAll(),
orders.getRecent(),
).wait; // Dart 3 record destructuring + Future.wait extension
return DashboardData(users: userList, orders: orderList);
}Stream Patterns
// Repository exposes reactive streams for live data
Stream<List<Item>> watchCartItems() => _db
.watchTable('cart_items')
.map((rows) => rows.Your agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /agent-architecture-audit
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for
Open skill - /agent-eval
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

