Loading...
Loading...
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.
npx skill4agent add zakariaf/flutter-skills flutter-conventions-indexproject-structure-and-packagescodegen-and-toolchainflutter-architectureproject-structure-and-packagesbuild()widget-compositionNotifierAsyncNotifierstate-management-riverpodNotifierAsyncNotifierFutureStreamget_itpackage:providerStateProviderStateNotifierProviderChangeNotifierProviderstate-management-riverpodapp-startup-and-bootstrapstate-management-riverpodpersistence-driftflutter-architectureservice-boundary-and-nativefreezedcopyWithResult<T, F extends Failure>catch (_) {}error-handling-typed-resultsdart3-idioms-and-coding-standardsClockDateTime.now()service-boundary-and-nativevalue-objects-money-and-unitsawaitFutureBuildContextmountedawaitFutureasync-safetydart3-idioms-and-coding-standardsbuild()constStatelessWidget_buildX()constdart3-idioms-and-coding-standardswidget-compositionflutter-performance…Screen…Notifier…Repository…Service…Failurenaming-conventionsstartendgen_l10ni18n-rtl-l10naccessibility-as-codepackage:testflutter_testmocktailProviderContainer.test()overrideWithNotifiertesting-strategywidget-golden-and-a11y-testingdart formatdart analyze --fatal-infosvery_good_analysisstrict-castsstrict-raw-typeslint-and-style-configdependency-hygiene| When you are… | Open |
|---|---|
| Orienting, unsure which skill governs the task | |
| Deciding layers, features-vs-packages, the dependency DAG | |
Scaffolding packages, barrels, | |
Writing | |
Writing a | |
Building or refactoring UI, writing | |
| Configuring the app router, redirects/auth guards, deep links, nav shells, transitions, PopScope, 404 | |
Building a | |
| Rendering loading/empty/error states, snackbars, banners, dialogs, Undo, retry | |
Responsive breakpoints, large-screen/tablet/foldable master-detail, | |
| Choosing a Dart 3 construct (sealed, records, class modifiers) | |
| Naming a class/file/variable/boolean | |
Configuring | |
| Fixing jank, narrowing rebuilds, lazy lists, off-isolate work | |
Writing | |
Writing async code, guarding | |
Modeling errors, | |
| Writing unit/widget tests, setting up fakes | |
| Writing golden, layout, RTL, or accessibility widget tests | |
Adding a Drift table, DAO, or | |
| Writing a forward-only schema migration | |
| Exporting, backing up, sharing, importing, or restoring user data | |
Setting up | |
| Running codegen before analyze | |
| Writing the GitHub Actions CI pipeline and gates | |
| Adding ARB strings, ICU plurals, RTL geometry | |
Authoring | |
| Modeling money, units, dates in a pure core | |
| Building on-device reminders/notifications | |
Writing a | |
| Adding an animation, a haptic, a celebration, or a success/error moment | |
| Generating content from a seed or date every device must reproduce identically | |
| Adding ads, a rewarded earn loop, a paywall, or an entitlement gate | |
| Wiring a native channel or platform side effect | |
Editing | |
| Scaffolding a whole feature module end to end | |
| Structuring tokens → theme → components (no aesthetic values) | |
| Re-baselining committed golden images | |
| Running the once-per-app design/QA review before release | |
| Cutting a release: versioning, signing, symbols, store declarations, rollout | |
dart3-idioms-and-coding-standardsvalue-objects-money-and-unitspersistence-drifterror-handling-typed-resultsservice-boundary-and-nativeNotifierAsyncNotifierfamilyautoDisposestate-management-riverpodConsumerWidgetconstwidget-compositionui-states-and-feedbackforms-and-inputadaptive-layouti18n-rtl-l10naccessibility-as-codego_routerapp-startup-and-bootstrapnavigation-and-routingscaffold-feature-module///dartdoc-conventionstesting-strategywidget-golden-and-a11y-testingflutter-performancecodegen-and-toolchainci-pipeline-and-gatesdesign-review-workflowrelease-and-store-shippingnaming-conventionslint-and-style-config# 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// 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>> {
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});
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),
);
}
}build()_buildXxx()setStateget_itpackage:providerStateProviderStateNotifierProviderChangeNotifierProviderDateTime.now()Clockcatch (_) {}Future!latedynamic// ignoredart formatNotifierAsyncNotifierResultFailureClockcontextmountedconst///dart formatdart analyze --fatal-infosflutter-architecturestate-management-riverpoderror-handling-typed-resultsResultFailure