building-flutter-apps

Flutter clean architecture with Riverpod 3.x codegen, Freezed 3.x sealed classes, GoRouter, Hive CE persistence, and ShowcaseView guided tours. Use when building, reviewing, or refactoring Flutter apps that use Riverpod for state management. Covers feature module scaffolding, AsyncNotifier patterns, provider select optimization, Freezed unions and JSON serialization, GoRouter redirects, Hive repositories, pagination, forms, anti-patterns, and testing. Does NOT apply to Provider/BLoC/GetX, non-Flutter frameworks, backend-only Dart, or Firebase-only questions.

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 "building-flutter-apps" with this command: npx skills add sgaabdu4/building-flutter-apps/sgaabdu4-building-flutter-apps-building-flutter-apps

Flutter Best Practices

Flutter clean architecture skill using Riverpod 3.x (codegen), Freezed 3.x (sealed classes), and GoRouter.

Core Stack

PackagePurpose
flutter_riverpod + riverpod_annotation + riverpod_generatorState management (codegen)
freezed + freezed_annotationImmutable data classes, unions
go_router + go_router_builderDeclarative, type-safe routing
json_serializable + build_runnerJSON serialization + code generation
showcaseviewFirst-run guided tours
hive_ce + hive_ce_flutterLocal persistence

Architecture

Four layers. Dependencies flow inward: Presentation → Repository → Domain → Data.

lib/
├── core/           # Shared: theme, utils, widgets, navigation, services
├── features/       # Feature modules (auth, products, home, ...)
│   └── feature_x/
│       ├── data/           # Models, datasources (API/local)
│       ├── domain/         # Entities (pure Dart, no dependencies)
│       ├── repositories/   # Orchestrate data sources, map models → entities
│       └── presentation/   # Notifiers, screens, widgets
└── main.dart

Each layer has one job. Domain holds pure Dart entities. Data holds models (with toEntity()) and datasources. Repositories bridge them — mapping models→entities. Presentation manages state and UI. Always create separate data models and domain entities even for simple features; repositories call model.toEntity() to convert.

Critical Rules

  1. Codegen only — Use @riverpod / @Riverpod(keepAlive: true). Legacy providers (StateProvider, etc.) are deprecated and lack codegen benefits.
  2. Sealed classes — All Freezed classes use sealed class, not abstract class. Dart's sealed enables exhaustive switch — the compiler catches missing cases.
  3. No prop drilling — Child widgets watch providers directly. Passing state through constructors couples parent-child and bypasses select() optimization.
  4. Guard async — Check if (!ref.mounted) return; after every await in notifiers. The notifier may be disposed while the future is in flight.
  5. Single Ref — Riverpod 3.0 unified all Ref types. AutoDisposeRef, FutureProviderRef, ExampleRef no longer exist.
  6. Equality filtering — Providers use == to skip redundant notifications. Override updateShouldNotify only when default equality is insufficient.
  7. Select in leaves — Use ref.watch(provider.select((s) => s.field)) in leaf widgets. Watching full state rebuilds the widget on every field change.
  8. One file per class — Each entity, model, notifier, screen, and widget gets its own file. Avoids circular imports and keeps codegen output manageable.

Provider Decision Tree

Is it a repository, datasource, or service?
  → @Riverpod(keepAlive: true) — lives forever

Is it a feature notifier (manages mutable state)?
  → @Riverpod(keepAlive: true) class FeatureNotifier extends _$FeatureNotifier

Is it a computed value or one-time fetch?
  → @riverpod (auto-disposes when unused)

Does it need parameters?
  → Add parameters to the generated function (family via codegen)

Freezed Patterns

// Simple data class
@freezed
sealed class Product with _$Product {
  const Product._();  // Required for adding methods/getters

  const factory Product({
    required String id,
    required String name,
    @Default(0) int quantity,
  }) = _Product;

  factory Product.fromJson(Map<String, dynamic> json) => _$ProductFromJson(json);

  // Rich domain methods — derive from own fields
  bool get inStock => quantity > 0;
}

// Union type (exhaustive pattern matching)
@freezed
sealed class AuthState with _$AuthState {
  const factory AuthState.authenticated(User user) = Authenticated;
  const factory AuthState.unauthenticated() = Unauthenticated;
  const factory AuthState.loading() = AuthLoading;
}

Notifier Pattern

@Riverpod(keepAlive: true)
class ProductNotifier extends _$ProductNotifier {
  @override
  ProductState build() {
    _load();
    return const ProductState();
  }

  Future<void> _load() async {
    state = state.copyWith(isLoading: true);
    try {
      final items = await ref.read(productRepositoryProvider).fetchAll();
      if (!ref.mounted) return;
      state = state.copyWith(items: items, isLoading: false);
    } catch (e) {
      if (!ref.mounted) return;
      state = state.copyWith(isLoading: false, error: e.toString());
    }
  }
}

Code Generation

# Watch mode (recommended during development)
dart run build_runner watch -d

# One-time build
dart run build_runner build -d

# Clean build (resolve conflicts)
dart run build_runner clean && dart run build_runner build -d

Anti-Patterns

WrongRightWhy
StateProvider@riverpod codegenLegacy, moved to legacy.dart
abstract class with Freezedsealed classEnables exhaustive matching
Parent watches, passes to childChild watches directlyProp drilling
Missing ref.mounted checkif (!ref.mounted) return;Crash on disposed notifier
ref.read in initStateaddPostFrameCallback then readProvider not ready
AutoDisposeNotifierNotifier (unified in 3.0)Duplicate removed
ExampleRef ref in codegenRef ref in codegenRef subclasses removed
Try-catch at every layerCatch once in notifierUseless rethrows
context.go('/path') string routesconst MyRoute().go(context) typedNo compile-time safety
Anemic model + extraction in repo/datasourceRich Model with methods on the modelKeep behavior with data
Per-class @JsonSerializable(explicitToJson: true)explicit_to_json: true in build.yamlOne global config; no per-class annotations
Entity directly in datasourcesData Model with toEntity() in repositoryDomain stays pure; repo maps model→entity
@Freezed(toJson: true) when fromJson existsPlain @freezedFreezed auto-generates toJson when fromJson uses =>
Using context after awaitif (!context.mounted) return;Context may be invalid after async gap
Raw Map/List as .family paramUse Freezed object or primitives== fails on collections, breaks provider caching
Provider for ephemeral local stateStatefulWidget local stateProviders are for shared/cross-widget state
Omitting fields in remote data objectInclude every schema field in pushSilent default overwrites remote value
Hardcoding one scope in sync restoreIterate all scopes from centralized listPartial restore; other screens replay

Router, sync, and utility anti-patterns are in their reference files: common-patterns.md (GoRouter redirect, delta sync) | extensions-utilities.md (context extensions, SnackBarUtils)

Reference Files

Consult the relevant reference when working on that topic. Each file has a Contents line at the top.

TopicFileConsult when
Architecture layers, file structurearchitecture.mdCreating feature modules, choosing layers
Atomic design: tokens → pagesatomic-design.mdBuilding shared widgets in core/widgets/
Riverpod 3.x codegen patternsriverpod-codegen.mdWriting providers, mutations, lifecycle
Freezed sealed classes, unions, Rich Modelsfreezed-sealed.mdCreating entities, models, unions, serialization
State management, async, notifiersstate-management.mdWriting notifiers, error handling, cross-provider
Testing with ProviderContainer.testtesting.mdWriting unit or widget tests
Pagination, search, forms, delta synccommon-patterns.mdLists, search, forms, GoRouter, sync
Performance, rebuilds, optimizationperformance.mdDebugging slow rebuilds, memory
Keys, slivers, animations, isolates, accessibility, adaptiveflutter-optimizations.mdScrolling, animation, concurrency, a11y
Context extensions, string/date utils, validators, DRY utilitiesextensions-utilities.mdAdding utilities, extensions, validators
Hive CE persistence, @GenerateAdapters, TypeAdaptershive-persistence.mdLocal storage, Hive adapters
Showcase guided tours, mixin, v5 API, syncshowcase-tours.mdAdding tours, syncing tour state across devices

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.

Security

SPIRIT State Sync

State Preservation & Identity Resurrection Infrastructure Tool (SPIRIT). Preserves AI agent identity, memory, and projects to a private Git repository. NEW:...

Registry SourceRecently Updated
5040Profile unavailable
Coding

AI Corporate Training Video Factory — Build a Complete Employee Training Program in 2 Hours

Automates creation of complete corporate training programs with up-to-date curricula, professional videos, assessments, and automated employee delivery in 2...

Registry SourceRecently Updated
00Profile unavailable
Coding

MEMORIA: Persistent Memory Layer for AI Agents

Gives your OpenClaw agent persistent memory across every session. MEMORIA maintains a structured knowledge layer: who you are, what you're building, every de...

Registry SourceRecently Updated
2191Profile unavailable
Coding

client-onboard

Generate a complete client onboarding package from a project description. Creates project brief, tech stack, milestones, folder structure, CLAUDE.md, and a r...

Registry SourceRecently Updated
1640Profile unavailable