Flutter project conventions
Applies Flutter’s asynchronous, navigation and widget-lifecycle rules within the state-management patterns the project already uses.
flutter-conventions
See how it helpsWhen is it useful?
Use it when a Flutter feature rebuilds excessively, leaks controllers or updates a screen after it has disappeared.
What does it help you do?
- Preserves the project’s established state-management approach.
- Handles navigation, lifecycle and controller disposal explicitly.
- Checks mounted contexts after awaits and isolates expensive computation.
Try asking this
Fix a Flutter form that navigates after its screen has been closed. Start a delayed save and leave the screen before it completes. No disposed context should navigate or show a message, and controllers must be released.
Detailed documentation
Read the skill’s original instructions, examples and checks in their authored language.
Show less
Flutter & Dart
Build maintainable, 60/120fps Flutter applications with layered architecture, fine-grained widget rebuilds, sound null safety and idiomatic Dart.
1. Layered architecture and state management
- Follow the repository's single state-management pattern (Riverpod, Bloc/Cubit, Provider or ChangeNotifier + ViewModel)—never introduce a second competing state library.
- Separate UI and business logic:
- UI layer (Screens & Widgets): renders immutable state, handles layout/animations, and dispatches user intents. Keep
build()pure, synchronous and free of side effects or network calls. - Logic layer (Notifiers / Blocs / ViewModels): coordinates screen state (
AsyncValue/ sealed state classes), form validation and optimistic updates. - Data layer (Repositories & Services): encapsulates HTTP clients (
dio/http), local databases (drift/sqflite), secure storage (flutter_secure_storage) and platform channels.
- UI layer (Screens & Widgets): renders immutable state, handles layout/animations, and dispatches user intents. Keep
- Narrow rebuild scope:
- Extract reusable
StatelessWidget/ConsumerWidgetclasses rather than helper methods returningWidgetso Flutter's element tree short-circuits rebuilds. - Use
constconstructors everywhere possible and subscribe narrowly (ref.watch(provider.select((s) => s.field)),BlocSelector,context.select).
- Extract reusable
2. Navigation, predictive back and lifecycle
- Declarative routing (
go_router/ Navigator 2.0):- Pass compact path/query parameters (
state.pathParameters['id']) rather than non-serializableextraobjects so routes survive deep links and state restoration. - Centralize auth redirects using
GoRouter(redirect: ..., refreshListenable: authRepository)and usecontext.go()when replacing the navigation stack (post-login, post-checkout) vscontext.push()for drill-down screens.
- Pass compact path/query parameters (
- Android 14+ predictive back & pop interception:
- Use
PopScope<T>(canPop: !hasUnsavedChanges, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; ... })instead of the deprecatedWillPopScopeoronPopInvoked.
- Use
- Lifecycle & controller cleanup:
- Use
AppLifecycleListener(orWidgetsBindingObserver) to pause cameras/timers/streams ononPause/onInactiveand refresh ononResume. - Always dispose
TextEditingController,FocusNode,ScrollController,AnimationControllerandStreamSubscriptionindispose().
- Use
3. Async safety, Isolates and code generation
- Async context safety: never create a
FutureorStreaminsidebuild(). After anyawaitin a widget or stateful callback, checkif (!context.mounted) return;before callingNavigator,ScaffoldMessengerorTheme.of(context). - Offload heavy CPU work to Isolates: run heavy JSON parsing, encryption or image processing via
await Isolate.run(() => ...)orcompute(). When calling platform plugins from a background isolate, initialize the messenger first withBackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken). - Codegen & models: model domain data and union states with Dart 3
sealed class/ pattern matching or@freezed+@JsonSerializable. Never hand-edit.g.dartor.freezed.dartfiles—rundart run build_runner build --delete-conflicting-outputs.
4. Scrolling, adaptive layout and platform channels
- Use
ListView.builder,ListView.separatedorCustomScrollViewwithSliverList.builderfor dynamic lists; setitemExtentorprototypeItemwhen item heights are uniform. - Build responsive layouts with
LayoutBuilder,MediaQuery.sizeOf(context)andSafeArea(avoidingMediaQuery.of(context)when only size or padding is needed, so keyboard animations do not rebuild unrelated subtrees). - Prefer type-safe Pigeon (
@HostApi(),@FlutterApi()) or well-structuredMethodChannelwrappers that catchPlatformExceptionandMissingPluginException.
5. Verification
- Run
flutter analyze,dart format --output=none --set-exit-if-changed, unit tests and widget tests (testWidgets,pumpWidget,pumpAndSettle).
Before you finish
- Widgets use
constconstructors, narrow selectors and properdispose()cleanup. -
context.mountedchecked after everyawait; noFuture/Streamcreated inbuild(). -
PopScope(onPopInvokedWithResult) andgo_routerauth/deep-link rules respected. - Codegen updated,
flutter analyzeclean, andflutter testpasses.
Included files 2
LICENSE.txtSKILL.md
About this skill package
flutter-conventions
flutter-conventions: Build mobile apps that handle navigation, native integrations, offline data and interrupted sessions reliably.
Show less
flutter-conventions
flutter-conventions: Build mobile apps that handle navigation, native integrations, offline data and interrupted sessions reliably.
Includes the complete skill and its supporting files (2 files). The exact authored scope is preserved below.
Original skill description (source language)
Use when working on a Flutter or Dart app, widgets (StatelessWidget, StatefulWidget, Slivers), state management (Riverpod, Bloc/Cubit, Provider), go_router, AppLifecycleListener, PopScope, Isolates, platform channels/Pigeon, freezed/build_runner or flutter test.
Install the skill, review its source and trust settings, then enable it for the relevant project or agent.
Requirements
- Orqestria installed on your computer.
