| name | flutter |
| description | Flutter/Dart rules, patterns, and checklists. Load when Flutter is detected. |
Detection Signals
Load this skill when any of the following are found:
| Signal | File / Location |
|---|
| Flutter SDK in pubspec | pubspec.yaml → sdk: flutter |
| Main entry point | lib/main.dart |
| Flutter workspace | flutter key in pubspec.yaml |
| Platform directories | android/ + ios/ + lib/ together |
Project Structure
lib/
├── main.dart # App entry, environment setup, runApp()
├── app/ # MaterialApp/CupertinoApp, router, theme
├── features/ # Feature-first: each feature owns its own layers
│ └── auth/
│ ├── data/ # Repositories, data sources, DTOs
│ ├── domain/ # Entities, use cases, repository interfaces
│ └── presentation/ # Widgets, pages, state (BLoC/Riverpod/etc.)
├── core/ # Shared: DI setup, network client, error handling
├── shared/ # Reusable widgets and utilities
└── l10n/ # Localization ARB files
- Feature-first structure is strongly preferred over layer-first for apps with > 3 features
lib/ contains only Dart — never put platform-specific Swift/Kotlin in lib/
- Platform code goes in
android/, ios/, linux/, macos/, web/, windows/ respectively
State Management
→ Load skills/mobile/flutter/references/state-management.md when choosing or implementing state management (BLoC, Cubit, Riverpod, Provider, GetX).
Dart Best Practices
Null Safety
- All new code must be null-safe — never use
! (null assertion) without a preceding null check or a comment explaining why it is guaranteed non-null
- Prefer
?? and ?. over null assertions
- Use
late only for variables that are genuinely initialized before first use (e.g., in initState) — not as a way to defer null handling
Async / Await
- Always
await Futures — never fire-and-forget unless intentional (document with a comment)
- Use
Future.wait() for parallel async operations, not sequential await
- Wrap top-level async errors:
FlutterError.onError + PlatformDispatcher.instance.onError
Isolates (Heavy Computation)
Use compute() or Isolate.run() for operations that block the UI thread > 16 ms:
// Decode large JSON on a background isolate
final parsed = await compute(parseHeavyJson, rawJsonString);
- JSON decoding of large payloads, image processing, and cryptography must run on isolates
- Do not share mutable state between isolates — pass data by message (serializable types only)
Code Style
- Follow Effective Dart naming conventions:
lowerCamelCase for variables/functions, UpperCamelCase for types, SCREAMING_SNAKE_CASE for constants
- Enable and comply with
flutter_lints (or very_good_analysis for stricter projects)
- Max line length: 80 characters (enforced by formatter — run
dart format . before committing)
- Never suppress lints with
// ignore: without a comment explaining the reason
Widget Best Practices
→ Load skills/mobile/flutter/references/widgets.md when working with widget composition, rebuilds, performance, or platform-adaptive UI.
Navigation
→ Load skills/mobile/flutter/references/navigation.md when working with routing, deep links, or navigation guards.
Flavors (Multi-Environment)
Flavors allow separate configurations for dev, staging, and production without code changes.
// lib/core/config/app_config.dart
enum Flavor { development, staging, production }
class AppConfig {
static late Flavor flavor;
static late String apiBaseUrl;
static void setup(Flavor f) {
flavor = f;
apiBaseUrl = switch (f) {
Flavor.development => 'https://api.dev.example.com',
Flavor.staging => 'https://api.staging.example.com',
Flavor.production => 'https://api.example.com',
};
}
}
- Run with flavor:
flutter run --flavor development -t lib/main_development.dart
- Never use
if (kDebugMode) as a substitute for flavors — debug/release and dev/prod are orthogonal concerns
- CI must build each flavor separately and run tests against each
Platform Channels (Native Code)
Use platform channels only when a Dart/Flutter package does not exist for the required native API.
const _channel = MethodChannel('com.company.app/biometric');
Future<bool> authenticate() async {
try {
return await _channel.invokeMethod<bool>('authenticate') ?? false;
} on PlatformException catch (e) {
return false;
}
}
- Channel names must be namespaced:
com.company.app/feature
- Always handle
MissingPluginException and PlatformException on the Dart side
- Prefer Pigeon for type-safe, generated channel code in production apps
Integration Awareness
| Service | Detection | Action |
|---|
| Firebase | google-services.json / GoogleService-Info.plist | Use firebase_core, initialize before runApp() |
| Supabase | supabase_flutter dep | Initialize with Supabase.initialize() before runApp() |
| Crashlytics | firebase_crashlytics dep | Pass FlutterError.onError to Crashlytics in main() |
| Push (FCM) | firebase_messaging dep | Request permission; handle background messages via top-level function |
Testing
| Layer | Tool | Scope |
|---|
| Unit | flutter test + mocktail | Business logic, use cases, repositories |
| Widget | flutter_test + WidgetTester | Individual widget rendering and interaction |
| Golden | golden_toolkit | Visual regression — pixel-diff screenshots |
| Integration | integration_test package | Full app flow on simulator / device |
- Mock dependencies with
mocktail — never use real network or file I/O in unit/widget tests
- Golden tests: generate goldens on CI; fail on diff; regenerate intentionally with
--update-goldens
- Integration tests must run on a physical device or a CI device farm (Firebase Test Lab, BrowserStack)
- BLoC: test using
bloc_test — assert emitted states for each event
App Store & Play Store — Publication Checklist
Both Platforms
iOS (App Store)
Android (Play Store)