agentsclimarketplace

Flutter conventions index

Skill zakariaf/Flutter-Skills/skills/flutter-conventions-index

The repo front-door for a Flutter/Dart app — the cross-cutting house rules (feature-first layered MVVM, immutable state with a single write path, Riverpod 3.x for state + DI, typed Result/Failure errors, dumb widgets, injected side effects, complexity limits) plus a routing table that sends each task to its deep-dive skill and a recommended feature build order. Use at the start of any Flutter/Dart work, before writing or reviewing a feature, when deciding which layer or package code belongs in, when unsure which skill governs a task (architecture, state, widgets, persistence, testing, i18n, design), or when onboarding to the conventions.From its SKILL.md

Install
npx -y skills add zakariaf/Flutter-Skills --skill flutter-conventions-index

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 25 days oldThe repository was created 25 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

16.0 KB, ~3.8k tokens by cl100k_base, as published. Nobody here has run it

Flutter Conventions — Index

The front door for this Flutter/Dart app. It states the cross-cutting house rules every task obeys, then routes each concern to a focused skill. For any non-trivial task: apply the rules here, then open the specialized skill for depth. This is the only skill that names every other skill in the library.

Assumes a single-package Flutter app by default. Monorepo / pub-workspace guidance is fenced inside the skills that own it (project-structure-and-packages, codegen-and-toolchain) — never required for a small app.

Non-negotiable rules

  1. Feature-first, layered, downward-only. Group by feature (a folder), then by layer: View → ViewModel → Repository → Service/data. Lower layers never import upward; the dependency graph is a strict DAG. WHY: an acyclic downward graph is the only structural guard against a big ball of mud. (flutter-architecture, project-structure-and-packages)
  2. Widgets are dumb. No business logic, data access, math, or formatting in a widget — it reads state and renders. WHY: logic in build() is untestable and rebuilds unpredictably. (widget-composition)
  3. One ViewModel per screen, over immutable state. A single Notifier/AsyncNotifier owns private mutable state and exposes it as an immutable value with value equality; a transition assigns a new state, never mutates in place. WHY: immutable value + single owner makes every change diffable and testable. (state-management-riverpod)
  4. Riverpod 3.x is state + DI. Modern Notifier/AsyncNotifier/Future/Stream providers only; providers are the DI container. No get_it, no package:provider, no legacy StateProvider/StateNotifierProvider/ChangeNotifierProvider. WHY: one composition model, no second DI framework to reconcile. (state-management-riverpod, app-startup-and-bootstrap)
  5. Single write path. A widget or ViewModel never mutates persisted state directly; every mutation is a repository method that persists first, then republishes via a stream. WHY: one durable, observable route means state is never half-written. (state-management-riverpod, persistence-drift)
  6. Derive, don't store; depend on abstractions. Compute derived values on read instead of caching a second copy; program against interfaces you inject, not concretes. WHY: a duplicated source of truth drifts out of sync; injected seams keep code testable. (flutter-architecture, service-boundary-and-native)
  7. Immutable models, typed errors. Domain and UI state are freezed/sealed value types with copyWith and value equality. Pure functions are total — they return uncertainty, never throw. Recoverable I/O returns a sealed Result<T, F extends Failure>, switched exhaustively; never a swallowed catch (_) {}. WHY: the compiler enforces every case; failures carry a stable code, not a localized string. (error-handling-typed-results, dart3-idioms-and-coding-standards)
  8. Side effects behind injected interfaces. Every platform/native effect (clock, notifications, share, analytics, storage) is a Dart interface behind a provider, overridden once at the composition root, faked in tests. Read "now" from an injected Clock, never DateTime.now(). WHY: a global side effect is a non-deterministic, untestable dependency. (service-boundary-and-native, value-objects-money-and-units)
  9. Async is never silent. await everything or handle the Future explicitly; no fire-and-forget arrow callbacks. Guard BuildContext/mounted after every await; dispose controllers, subscriptions, timers, and sinks. WHY: a dropped Future swallows errors no lint catches. (async-safety)
  10. Small units, extracted widget classes. Keep to the complexity-limit table owned by dart3-idioms-and-coding-standards (method ≤30, build() ≤80, file ≤300, positional params ≤3, logic nesting ≤3 — widget build trees may nest to ≤5 as the stated exception). Extract const StatelessWidget classes, never _buildX() methods; const everywhere legal. WHY: small const subtrees rebuild less and read faster. (dart3-idioms-and-coding-standards, widget-composition, flutter-performance)
  11. Names carry roles. …Screen/…Notifier/…Repository/…Service/…Failure; Effective-Dart casing verbatim; full words with units in the name; file named after its primary declaration. WHY: a grep or a filename should reveal the layer. (naming-conventions)
  12. RTL and a11y by construction. Use directional (start/end) geometry only, never hardcoded left/right; every user string comes from an ARB via gen_l10n; label and role every semantic node; never clamp the text scaler or rely on color alone. WHY: correctness properties are cheap to build in and expensive to retrofit. (i18n-rtl-l10n, accessibility-as-code)
  13. Test the shape of the code, not a fixed ratio. Pure core: fast, clock-injected package:test + property invariants. Everything else: flutter_test + mocktail, ProviderContainer.test() + overrideWith — fake the repository/services, not the Notifier. One acceptance test anchors the whole app. WHY: tests follow risk; the pure core carries the invariants. (testing-strategy, widget-golden-and-a11y-testing)
  14. Strict lint is the floor; format is not negotiable. dart format clean and dart analyze --fatal-infos green before every PR, on a version-pinned very_good_analysis include with strict-casts/strict-raw-types. WHY: a green analyzer is the cheapest correctness signal you have. (lint-and-style-config, dependency-hygiene)

Route to the right skill

When you are…Open
Orienting, unsure which skill governs the taskflutter-conventions-index (this)
Deciding layers, features-vs-packages, the dependency DAGflutter-architecture
Scaffolding packages, barrels, lib/ vs lib/src/, workspace layoutproject-structure-and-packages
Writing main(), error handlers, DI overrides, warm-up, app-lifecycle flush-on-background/resumeapp-startup-and-bootstrap
Writing a Notifier/AsyncNotifier, family/autoDispose, the single write pathstate-management-riverpod
Building or refactoring UI, writing build(), layout/insetswidget-composition
Configuring the app router, redirects/auth guards, deep links, nav shells, transitions, PopScope, 404navigation-and-routing
Building a Form, sync/async field validation, focus traversal, keyboard actions, input formattersforms-and-input
Responsive breakpoints, large-screen/tablet/foldable master-detail, NavigationRail-vs-BottomNavigationBar by widthadaptive-layout
Choosing a Dart 3 construct (sealed, records, class modifiers)dart3-idioms-and-coding-standards
Naming a class/file/variable/booleannaming-conventions
Configuring analysis_options.yaml, lint severitylint-and-style-config
Fixing jank, narrowing rebuilds, lazy lists, off-isolate workflutter-performance
Writing /// doc comments on the public surfacedartdoc-conventions
Writing async code, guarding context/mounted, disposalasync-safety
Modeling errors, Result/Failure, never-lose-data flowserror-handling-typed-results
Writing unit/widget tests, setting up fakestesting-strategy
Writing golden, layout, RTL, or accessibility widget testswidget-golden-and-a11y-testing
Adding a Drift table, DAO, or .watch streampersistence-drift
Writing a forward-only schema migrationrun-migration
Setting up build_runner, build.yaml, generated-code policycodegen-and-toolchain
Running codegen before analyzerun-codegen
Writing the GitHub Actions CI pipeline and gatesci-pipeline-and-gates
Adding ARB strings, ICU plurals, RTL geometryi18n-rtl-l10n
Authoring Semantics, targets, traversal, text scalingaccessibility-as-code
Modeling money, units, dates in a pure corevalue-objects-money-and-units
Building on-device reminders/notificationslocal-notifications-scheduler
Writing a CustomPainter + gesture hit-testingcustom-canvas-and-gestures
Wiring a native channel or platform side effectservice-boundary-and-native
Editing pubspec/lockfile, auditing a new dependencydependency-hygiene
Scaffolding a whole feature module end to endscaffold-feature-module
Structuring tokens → theme → components (no aesthetic values)design-system-structure
Running the once-per-app design/QA review before releasedesign-review-workflow

Recommended order when building a feature

  1. Model + pure functions — immutable value types; total, clock-injected core → dart3-idioms-and-coding-standards, value-objects-money-and-units
  2. Data layer — repository = single source of truth / single write path; Drift table + DAO if persisted → persistence-drift, error-handling-typed-results
  3. Service seams — every side effect behind an injected interface → service-boundary-and-native
  4. ViewModelNotifier/AsyncNotifier, family/autoDispose, intent methods → state-management-riverpod
  5. View + widgets — dumb ConsumerWidget, small const widget classes, forms, responsive layout, RTL-safe geometry → widget-composition, forms-and-input, adaptive-layout, i18n-rtl-l10n, accessibility-as-code
  6. Wire DI + routing — providers overridden at the composition root; the feature route registers into the single go_routerapp-startup-and-bootstrap, navigation-and-routing, scaffold-feature-module
  7. Docs + tests/// on the public surface; core invariants + container/widget tests with fakes → dartdoc-conventions, testing-strategy, widget-golden-and-a11y-testing
  8. Profile, then the CI gate — measure in profile mode; format/analyze/codegen/test green → flutter-performance, codegen-and-toolchain, ci-pipeline-and-gates

Throughout: naming-conventions and lint-and-style-config keep every line honest.

Baseline project setup

# analysis_options.yaml
include: package:very_good_analysis/analysis_options.yaml   # pin the version in pubspec
analyzer:
  language:
    strict-casts: true
    strict-raw-types: true
# The PR gate (mirror in CI — see ci-pipeline-and-gates)
dart run build_runner build --delete-conflicting-outputs   # if the app uses codegen
dart format --set-exit-if-changed .
dart analyze --fatal-infos --fatal-warnings
flutter test

The shape every feature repeats

// Repository = the SINGLE WRITE PATH: persist first, then observers re-emit.
class OrderRepository {
  OrderRepository(this._db, this._clock);
  final AppDatabase _db;
  final Clock _clock;

  /// The only route an order reaches storage. Returns a typed Result, never throws.
  Future<Result<void, PersistFailure>> place(OrderDraft draft) async {
    try {
      await _db.transaction(() async {
        await _db.orderDao.insert(draft.toRow(_clock.now())); // durable FIRST
      });
      return const Ok(null); // watchers over the DAO re-emit on commit
    } on DriftException catch (e, s) {
      return Err(PersistFailure.write(cause: e, stack: s));
    }
  }
}

// ViewModel = one StreamNotifier per screen; state tracks EVERY DAO emission.
// Riverpod 3.x: extend the unified base class, opt into autoDispose on the provider.
class OrderListNotifier extends StreamNotifier<List<Order>> {
  @override
  Stream<List<Order>> build() =>
      ref.watch(orderRepositoryProvider).recent(); // re-emits on every commit

  Future<void> place(OrderDraft draft) async {
    final result = await ref.read(orderRepositoryProvider).place(draft);
    switch (result) {
      case Ok():        break;            // the watched stream re-emits the new state
      case Err(:final failure): state = AsyncError(failure, StackTrace.current);
    }
  }
}

final orderListProvider =
    StreamNotifierProvider.autoDispose<OrderListNotifier, List<Order>>(
  OrderListNotifier.new,
);

// View = dumb ConsumerWidget: reads one notifier, renders, no logic.
class OrderListScreen extends ConsumerWidget {
  const OrderListScreen({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(orderListProvider).when(
      loading: () => const _OrdersSkeleton(),
      error: (e, _) => _RetryView(onRetry: () => ref.invalidate(orderListProvider)),
      data: (orders) => _OrderListView(orders: orders),
    );
  }
}

Anti-patterns

  • Data access or business logic inside a widget, a god-build(), or _buildXxx() helper methods — untestable and rebuild-unfriendly.
  • setState for app/shared state; a mutable model edited by the UI — breaks the single owner + immutable-state rule.
  • get_it/package:provider/legacy StateProvider/StateNotifierProvider/ChangeNotifierProvider — a second DI/state framework to reconcile.
  • A widget or ViewModel writing a DAO directly, or "republish then persist" — bypasses the single write path and can expose half-written state.
  • DateTime.now() in domain code — a non-deterministic dependency; inject a Clock.
  • A swallowed error (catch (_) {}), an untyped exception leaking to the UI, or a fire-and-forget Future — silent failure no lint catches.
  • Hardcoded left/right, a raw digit string, or a user-facing literal in a widget — breaks RTL and localization by construction.
  • !/late/dynamic to dodge honest types; blanket // ignore; fighting dart format — hides the real defect.
  • Optimizing without profiling, or profiling in debug mode — measures the wrong thing.

Definition of done

  • Code sits in the correct feature/layer; the dependency graph only points downward (rules 1, 6).
  • The View is dumb; one Notifier/AsyncNotifier owns immutable state; no legacy provider or extra DI container (rules 2–4).
  • Every mutation goes through a repository's single write path — persist, then republish (rule 5).
  • Models immutable with value equality; pure functions total; recoverable I/O is Result + sealed Failure, exhaustively switched; no swallowed errors (rule 7).
  • Side effects are behind injected interfaces; "now" comes from a Clock (rule 8).
  • Async is awaited/handled; context/mounted guarded after awaits; controllers and subscriptions disposed (rule 9).
  • Units within limits; widget classes extracted; const applied; names carry role + units (rules 10–11).
  • RTL-safe geometry; strings in ARB; semantics labeled; text scaler not clamped (rule 12).
  • Public surface has /// docs; core invariants + container/widget tests with fakes pass (rule 13).
  • dart format + dart analyze --fatal-infos clean on a pinned lint include (rule 14).
  • The right specialized skill was consulted for the depth of the task.

Related skills

Every skill in this table is a sibling; open the one the routing table points to. Start with flutter-architecture for where code goes, state-management-riverpod for how state flows, and error-handling-typed-results for the Result/Failure spine.

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most docs writing skills give in ~3.8k tokens

Counted across 1,637 of the 3,044 authors here whose files we hold, read 2026-08-07

  • Announce the skill at startin 54 of 1637, across 26 files
  • Convert legacy doc files before editingin 45 of 1637, across 7 files
  • Predict questions readers might askin 42 of 1637, across 4 files
  • Generate clarifying questions for initial contextin 42 of 1637, across 3 files
  • Create document scaffold with placeholder textin 42 of 1637, across 3 files
  • Brainstorm content options for each sectionin 42 of 1637, across 3 files
  • Test the document with a fresh context-less instancein 42 of 1637, across 3 files
  • Include exact file paths in every taskin 42 of 1637, across 15 files
  • Ask interview questions one at a timein 42 of 1637, across 27 files
  • Apply surgical edits during refinementin 41 of 1637, across 2 files
  • Offer structured workflow or freeformin 40 of 1637, across 1 file
  • Ask for document meta-contextin 40 of 1637, across 2 files

Said here and by no other author read

  • group code by feature then layer downward only
  • keep widgets free of business logic
  • own screen state via one immutable notifier
  • use Riverpod for state and dependency injection
  • route every persisted mutation through the repository
  • derive values on read instead of caching

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,422. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.