← Back to Mobile app developers
See how it helps
Flutter project conventions
Applies Flutter’s asynchronous, navigation and widget-lifecycle rules within the state-management patterns the project already uses.
flutter-conventions
Team members using this skill
When 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
