flutter-apps-skill

Flutter feature-first MVVM architecture with Riverpod 3.x code generation, Freezed 3.x sealed classes, GoRouter, Drift, Hive CE persistence, and ShowcaseView guided tours. Use this skill when building, reviewing, or refactoring Flutter apps that use Riverpod for state management. Covers feature-first structure, MVVM presentation patterns, state management, local storage, onboarding tours, performance, pagination, search, and forms.

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

Flutter Best Practices

Use this skill for Flutter apps built with a feature-first MVVM architecture, Riverpod 3.x code generation, Freezed 3.x sealed classes, and GoRouter.

Core Stack

PackageVersionPurpose
flutter_riverpod3.2.1+State management
riverpod_annotation3.xCode generation annotations
riverpod_generator3.xProvider code generation
freezed3.2.5+Immutable data classes and unions
freezed_annotation3.xFreezed annotations
go_router17.1.0+Declarative routing
go_router_builder4.2.0+Type-safe route code generation
drift2.32.0+SQLite ORM and reactive queries
drift_dev2.32.0+Drift code generation and migrations
drift_flutter0.3.0+Flutter helpers for Drift connections
sqlite3_flutter_libslatestNative SQLite binaries
showcaseview5.0.1+First-run guided tours
json_serializablelatestJSON serialization
build_runnerlatestCode generation runner

Architecture

Use a feature-first MVVM structure. Organize code by feature first, then split each feature into data, domain, repositories, and presentation.

Treat presentation/ as the MVVM layer:

  • Screens and widgets are the View.
  • Riverpod notifiers are the ViewModel.
  • domain/ holds pure business entities.
  • data/ holds models and data sources.
  • repositories/ orchestrate reads, writes, mapping, and caching for the feature.
lib/
|-- core/           # Shared code: theme, utils, widgets, navigation, services
|-- features/       # Feature modules (auth, products, home, ...)
|   `-- feature_x/
|       |-- data/           # Models and data sources (API/local)
|       |-- domain/         # Pure Dart entities with no framework dependencies
|       |-- repositories/   # Coordinate data sources and map models to entities
|       `-- presentation/   # Notifiers, screens, and widgets
`-- main.dart

Keep each layer focused on one responsibility. Domain defines entities. Data handles models and data sources. Repositories coordinate feature data flow. Presentation manages UI state and user interaction through MVVM-style notifiers.

Critical Rules

  1. Use code generation only. Prefer @riverpod or @Riverpod(keepAlive: true). Do not use StateProvider, StateNotifierProvider, or ChangeNotifierProvider.
  2. Use sealed classes with Freezed. Declare Freezed types as sealed class, not abstract class.
  3. Avoid prop drilling. Let child widgets watch providers directly instead of receiving provider state through constructors.
  4. Guard async work. After every await in a notifier, check if (!ref.mounted) return;.
  5. Use a single Ref type. Riverpod 3.x uses Ref. Do not use AutoDisposeRef, FutureProviderRef, or generated ref subtypes.
  6. Rely on equality filtering. Providers already use == to filter updates. Override updateShouldNotify only when necessary.
  7. Use select in leaf widgets. Prefer ref.watch(provider.select((s) => s.field)) when a widget needs only one field.
  8. Keep one class per file. Each entity, model, notifier, screen, and widget should live in its own file.
  9. Stay feature-first. Do not move feature-specific code into app-wide type folders.
  10. Keep MVVM boundaries clear. Put UI logic in widgets and Riverpod notifiers, not in repositories or datasources.

Provider Decision Tree

Is it a repository, datasource, or service?
  -> Use @Riverpod(keepAlive: true) so it stays alive

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

Is it a computed value or one-time fetch?
  -> Use @riverpod so it auto-disposes when unused

Does it need parameters?
  -> Add parameters to the generated function and let codegen create the family

Freezed Patterns

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

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

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

  // Keep domain behavior on the model itself.
  bool get inStock => quantity > 0;
}

// Union type with 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 to resolve conflicts
dart run build_runner clean && dart run build_runner build -d

Quick Imports

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart';
import 'package:my_app/core/extensions/extensions.dart'; // Context, string, and date extensions

// In route files
part 'routes.g.dart';

Anti-Patterns

AvoidPreferWhy
StateProvider@riverpod code generationStateProvider is legacy and moved to legacy.dart
abstract class with Freezedsealed classEnables exhaustive pattern matching
Parent watches and passes state to childChild watches directlyAvoids prop drilling
Missing ref.mounted checkif (!ref.mounted) return;Prevents updates after disposal
ref.read in initStateaddPostFrameCallback and then readEnsures the provider is ready
AutoDisposeNotifierNotifier in Riverpod 3.xOld duplication was removed
ExampleRef ref in generated codeRef refRef subclasses were removed
try-catch in every layerCatch once in the notifierAvoids repetitive rethrows
context.go('/path') string routesconst MyRoute().go(context)Gives compile-time safety
context.go() between peer routes just to update the URLGoRouter.optionURLReflectsImperativeAPIs = true with push/popPreserves directional animation while still updating the browser URL
Redirect every loading state to splashReturn null during loading for non-setup pagesPrevents losing the current web URL during refresh
Depend only on GoRouter redirect after loginAdd ref.listen(authProvider) in auth pages as a fallbackRedirect timing can be unreliable
Use auth-level isLoading during OAuthUse per-button loading such as isGoogleLoadingPrevents premature splash redirects
ref.watch() inside GoRouter redirectref.listen() plus refreshListenable, then ref.read() in redirectPrevents router recreation and stack resets
Full re-fetch on every syncmergeAll plus ID diff with deleteByIdsReduces work from all records to changed records only
Theme.of(context).colorSchemecontext.colorsKeeps color access consistent through extensions
ScaffoldMessenger.of(context)SnackBarUtils.showSuccess()Centralizes feedback and avoids context plumbing
Anemic model with mapping logic elsewhereRich model with methods on the modelKeeps behavior close to data
Missing @JsonSerializable(explicitToJson: true) on nested Freezed modelsAdd it on the factory constructorPrevents release crashes such as _XYZ is not a subtype of Map

Reference Files

Read only the reference file that matches the task.

TopicFile
Architecture layers and file structurearchitecture.md
Atomic design from tokens to pagesatomic-design.md
Riverpod 3.x code generation patternsriverpod-codegen.md
Freezed sealed classes, unions, and rich modelsfreezed-sealed.md
State management, async flows, and notifiersstate-management.md
Pagination, search, forms, and delta synccommon-patterns.md
Performance, rebuilds, and optimizationperformance.md
Keys, slivers, animations, isolates, accessibility, and adaptive UIflutter-optimizations.md
Context extensions, string and date utilities, validators, and DRY helpersextensions-utilities.md
Hive CE persistence, @GenerateAdapters, and TypeAdaptershive-persistence.md
Drift persistence, scalable structure, and migrationsdrift-persistence.md
Showcase guided tours, mixins, and the v5 APIshowcase-tours.md

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.

Coding

typescript-strict

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

api-client

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

ai-generation-client

No summary provided by upstream source.

Repository SourceNeeds Review