skills/hukusuke1007/agents-skills/flutter-riverpod-arch

flutter-riverpod-arch

SKILL.md

Flutter Riverpod Architecture

Goal

Implements scalable Flutter applications using the Feature-First directory structure, Riverpod (with code generation) for state management, and flutter_hooks / HookConsumerWidget for UI composition. Organizes code into four layers — UI, Use Case, Repository, and Data Source — enforcing unidirectional data flow. Applies @Riverpod(keepAlive: true) for global/repository providers and @riverpod for screen-scoped state.

Decision Logic

Before implementing, evaluate the following to determine the correct approach:

Provider scope

  • Does the state need to persist across screen navigations, or is it a repository / auth state shared across features?
    • Yes → Use @Riverpod(keepAlive: true)
    • No → Use @riverpod (auto-disposed when no longer watched)

Repository vs. Use Case

  • Does the operation simply read/write a single data source with no business rules?
    • Yes → Implement directly in the Repository; the UI provider calls the repository directly.
  • Does the operation combine multiple repositories, require validation, or orchestrate a multi-step process?
    • Yes → Create a dedicated Use Case class inside providers/.

Feature vs. Core placement

  • Is the provider / repository used by only one feature?
    • Yes → Place in features/{feature}/providers/
    • No → Place in core/providers/

Directory structure within providers/

  • Fewer than ~5 files in a feature's providers/?
    • Yes → Keep flat (repository + use cases together).
    • No → Split into providers/repositories/ and providers/use_cases/ subdirectories.

Instructions

1. Plan the Feature Structure

Identify the feature module, the providers required, and the correct layer for each piece of logic.

STOP AND ASK THE USER: "Which feature does this belong to? Is the data source a remote API, local database, or both? Does the logic require combining multiple data sources or complex validation that warrants a dedicated Use Case?"

Place code according to this structure:

lib/
  core/
    providers/       # Shared repositories and providers across features
    widgets/         # Reusable widgets
    extensions/      # Extension methods
    res/             # Colors, text styles, theme
  features/
    {feature}/
      providers/     # Repositories and use cases for this feature
      pages/
        widgets/     # Page-specific widgets
      {feature}_page.dart

Validate-and-Fix: Confirm that no provider is placed inside pages/. Confirm that shared providers live in core/providers/, not duplicated across features.


2. Implement the Data Layer: Repository

Create a repository class that encapsulates all access to a single data source (API, local DB). Annotate its provider with @Riverpod(keepAlive: true). After adding annotations, run code generation:

dart run build_runner build

See providers.md for the full implementation pattern.

Validate-and-Fix: Confirm the repository contains no business logic (no validation, no multi-source orchestration). Confirm the provider function returns only the repository instance, and that @Riverpod(keepAlive: true) is used (not @riverpod).


3. Implement Business Logic: Use Cases

For operations that involve validation, multi-step orchestration, or combining repositories, create a dedicated Use Case class inside providers/. Three patterns:

  • Async data fetcherclass FetchPosts extends _$FetchPosts with Future<T> build()
  • Action use case (callable class)CreatePost with call() for commands; invalidate dependent providers after writes
  • Stateful controllerclass PostController extends _$PostController for screen-scoped UI state

See providers.md for full implementation patterns.

Validate-and-Fix: Confirm each use case handles only one responsibility. Confirm that ref.invalidate() is called after mutations to keep dependent providers fresh. Confirm @riverpod (not keepAlive) is used for screen-scoped use cases.


4. Build the UI with HookConsumerWidget

Extend HookConsumerWidget for all stateful pages and complex widgets. Use hooks (useScrollController, useTextEditingController, etc.) for local UI state. Provide a static show() method for type-safe navigation. When using a declarative router (GoRouter, auto_route), replace show() with the router's push API.

See presentation.md for page structure and hooks patterns. For mouse cursor requirements on macOS/Web see button.md.

Validate-and-Fix: Confirm that ref.watch() is never called inside event handlers (onPressed, onTap, etc.). Confirm that ref.read() is never called at the top of build() to derive reactive state. Confirm HookConsumerWidget is used for all stateful pages and complex widgets. Confirm macOS/Web interactive widgets include enabledMouseCursor or mouseCursor.


5. Implement Responsive Layout

Use ResponsiveLayout for page-level breakpoints based on MediaQuery.sizeOf(context). Treat tablet and macOS as the same layout tier. Use LayoutBuilder only for widget-level (not page-level) breakpoints.

See responsive.md for breakpoints, ResponsiveLayout implementation, and platform-specific patterns.

Validate-and-Fix: Confirm that LayoutBuilder is not used for page-level breakpoint decisions. Confirm that targeted MediaQuery static methods (MediaQuery.sizeOf, MediaQuery.paddingOf, MediaQuery.orientationOf) are used instead of MediaQuery.of(context) to avoid unnecessary rebuilds.


6. Write Tests

Use ProviderContainer with overrides to test providers and use cases in isolation. Define a createContainer helper in test/utils.dart. Use mockito for mocking external dependencies.

See testing.md for full test patterns (repository, use case, controller, widget tests).

Validate-and-Fix: Confirm that every test overrides all external dependencies (API clients, databases). Confirm that addTearDown(container.dispose) is called for every ProviderContainer. Confirm widget tests use ProviderScope with overrides rather than calling real repositories.


Constraints

  • Prefer HookConsumerWidget: This skill uses HookConsumerWidget (with flutter_hooks) for all stateful pages and complex widgets. StatefulWidget may be used for isolated, purely local UI state with no Riverpod interaction.
  • No ref.read() in build: ref.read() must only appear inside event handlers or callbacks, never at the top of a build method to derive reactive UI state.
  • No ref.watch() in event handlers: ref.watch() must only appear inside build() or a provider's build() method.
  • No business logic in Views: Widget classes must contain only layout, conditional rendering, and navigation. All data transformation and validation must reside in Use Cases or Repositories.
  • No SingleChildScrollView + Column for dynamic lists: Always use ListView.builder (or SliverList inside CustomScrollView) for lists of dynamic length.
  • No button wrapper widgets: Never create a custom wrapper widget for a single button purpose. Configure appearance via styleFrom or a shared AppButtonStyle utility class.
  • Use targeted MediaQuery static methods: Prefer MediaQuery.sizeOf, MediaQuery.paddingOf, MediaQuery.orientationOf etc. over MediaQuery.of(context) to avoid unnecessary rebuilds.
  • No LayoutBuilder for page-level breakpoints: Use ResponsiveLayout (which uses MediaQuery.sizeOf) so the layout decision is based on actual screen width, not parent constraints.
  • Mouse cursors required on macOS/Web: Every Button variant must include enabledMouseCursor: SystemMouseCursors.click. Every InkWell must include mouseCursor: SystemMouseCursors.click.
  • Code generation required: After adding or modifying a @riverpod annotation, always run dart run build_runner build.
  • Naming conventions: Files and directories use snake_case; classes use UpperCamelCase; page classes end in Page; repository classes end in Repository; use case classes use verb-noun format (e.g., FetchPosts, CreatePost).

Reference Files

  • architecture.md — Project structure, directory layout, naming conventions
  • providers.md — Repository and use case implementation patterns
  • riverpod.md — Provider types, ref.watch vs ref.read, caching, code generation
  • presentation.md — Page structure, navigation, hooks usage, error handling
  • testing.md — Test patterns, mocking strategy, ProviderContainer usage
  • button.md — Button patterns, styleFrom, hover cursor for macOS/Web
  • list.mdListView.builder, Pull-to-Refresh, pagination with Slivers
  • responsive.md — Breakpoints, ResponsiveLayout, platform-specific layouts
Weekly Installs
1
First Seen
12 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1