Flutter Developer Skill
Expert assistance for Flutter development covering modern patterns, architecture, and best practices.
When to Use This Skill
-
Building or modifying Flutter applications
-
Creating custom widgets and layouts
-
Implementing navigation and routing
-
Working with Flutter animations
-
Handling platform-specific functionality
-
Optimizing Flutter app performance
-
Setting up Flutter project structure
-
Implementing responsive and adaptive designs
-
Managing Flutter dependencies and packages
Core Principles
- Widget Composition
-
Prefer composition over inheritance
-
Break complex widgets into smaller, reusable components
-
Use const constructors whenever possible for performance
-
Follow the single responsibility principle for widgets
- State Management Readiness
-
Design widgets to be compatible with any state management solution
-
Separate business logic from UI code
-
Use StatelessWidget by default, StatefulWidget only when needed
-
Make widgets testable and independent
- Performance Best Practices
-
Use const widgets to reduce rebuilds
-
Implement RepaintBoundary for complex custom painters
-
Use ListView.builder for long lists
-
Avoid unnecessary rebuilds with proper widget keys
-
Profile with Flutter DevTools before optimizing
- Clean Architecture
lib/ ├── core/ # Core utilities, constants, extensions ├── features/ # Feature modules │ └── feature_name/ │ ├── data/ # Data sources, repositories │ ├── domain/ # Entities, use cases │ └── presentation/ # UI, widgets, pages ├── shared/ # Shared widgets, utilities └── main.dart
Project-Specific Guidelines
Required Packages & Usage
IMPORTANT: These are mandatory patterns for this project:
- HTTP Client - Dio (Required)
-
ALWAYS use Dio for all API calls
-
Never use http package directly for API requests
-
Configure Dio with interceptors for logging, auth, error handling
import 'package:dio/dio.dart';
final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), ));
// Add interceptors dio.interceptors.add(LogInterceptor(responseBody: true));
// Example API call Future<User> fetchUser(String id) async { final response = await dio.get('/users/$id'); return User.fromJson(response.data); }
- Data Storage - Hive (Required for non-sensitive data)
-
Use Hive for caching and storing non-sensitive data
-
Never store sensitive data (tokens, passwords, personal info) in Hive
-
Use TypeAdapters for complex objects
import 'package:hive_flutter/hive_flutter.dart';
// Initialize Hive await Hive.initFlutter(); Hive.registerAdapter(UserAdapter());
// Open box final box = await Hive.openBox<User>('users');
// Store data await box.put('user_1', user);
// Retrieve data final user = box.get('user_1');
- Secure Storage - flutter_secure_storage (Required for sensitive data)
-
ALWAYS use flutter_secure_storage for sensitive data
-
Use for: auth tokens, API keys, passwords, personal information
-
Never store sensitive data in SharedPreferences or Hive
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
const storage = FlutterSecureStorage();
// Store sensitive data await storage.write(key: 'auth_token', value: token); await storage.write(key: 'refresh_token', value: refreshToken);
// Retrieve sensitive data final token = await storage.read(key: 'auth_token');
// Delete sensitive data await storage.delete(key: 'auth_token');
- Localization - easy_localization (Required)
-
Use easy_localization for all internationalization
-
CRITICAL RULE: Translation keys must NEVER be plain strings
-
Always use type-safe key classes
import 'package:easy_localization/easy_localization.dart';
// ❌ WRONG - Never use string literals Text('welcome_message'.tr())
// ✅ CORRECT - Always use LocaleKeys Text(LocaleKeys.home_welcomeMessage.tr())
// Plural support Text(LocaleKeys.items_count.plural(count))
// Parameters Text(LocaleKeys.greeting_message.tr(args: [userName]))
Locale Keys Structure:
// Generated from translations abstract class LocaleKeys { static const home_welcomeMessage = 'home.welcomeMessage'; static const home_subtitle = 'home.subtitle'; static const items_count = 'items.count'; static const greeting_message = 'greeting.message'; }
- Code Generation - Freezed & Generators (Required)
-
ALWAYS use Freezed for immutable data classes
-
Use json_serializable for JSON serialization
-
Run flutter pub run build_runner build after changes
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart'; part 'user.g.dart';
@freezed class User with _$User { const factory User({ required String id, required String name, required String email, String? avatarUrl, }) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); }
// Usage final user = User(id: '1', name: 'John', email: 'john@example.com');
// CopyWith final updatedUser = user.copyWith(name: 'Jane');
// Equality (automatic) print(user == updatedUser); // false
Data Storage Decision Tree
Need to store data? ├─ Is it sensitive? (tokens, passwords, personal info) │ ├─ YES → Use flutter_secure_storage │ └─ NO → Continue ├─ Is it simple key-value? (settings, flags) │ ├─ YES → Use SharedPreferences (only for non-sensitive) │ └─ NO → Continue └─ Is it structured data? (objects, lists) └─ YES → Use Hive with TypeAdapters
API Integration Pattern
// 1. Define data model with Freezed @freezed class Product with _$Product { const factory Product({ required String id, required String name, required double price, }) = _Product;
factory Product.fromJson(Map<String, dynamic> json) => _$ProductFromJson(json); }
// 2. Create repository with Dio class ProductRepository { final Dio _dio;
ProductRepository(this._dio);
Future<List<Product>> fetchProducts() async { final response = await _dio.get('/products'); return (response.data as List) .map((json) => Product.fromJson(json)) .toList(); }
Future<void> cacheProducts(List<Product> products) async { final box = Hive.box<Product>('products'); await box.clear(); await box.addAll(products); }
List<Product> getCachedProducts() { final box = Hive.box<Product>('products'); return box.values.toList(); } }
Localization Setup
// main.dart void main() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized();
runApp( EasyLocalization( supportedLocales: const [Locale('en'), Locale('es'), Locale('ar')], path: 'assets/translations', fallbackLocale: const Locale('en'), child: const MyApp(), ), ); }
// In widgets class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ // Simple translation Text(LocaleKeys.common_save.tr()),
// With parameters
Text(LocaleKeys.greeting_hello.tr(args: [userName])),
// Plurals
Text(LocaleKeys.items_count.plural(itemCount)),
// Gender
Text(LocaleKeys.user_title.tr(gender: 'male')),
],
);
} }
Common Patterns
Widget Structure
class MyWidget extends StatelessWidget { const MyWidget({ super.key, required this.title, this.onTap, });
final String title; final VoidCallback? onTap;
@override Widget build(BuildContext context) { final theme = Theme.of(context);
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
child: Text(
title,
style: theme.textTheme.titleMedium,
),
),
);
} }
Responsive Design
class ResponsiveLayout extends StatelessWidget { const ResponsiveLayout({ super.key, required this.mobile, this.tablet, this.desktop, });
final Widget mobile; final Widget? tablet; final Widget? desktop;
@override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth >= 1200 && desktop != null) { return desktop!; } else if (constraints.maxWidth >= 600 && tablet != null) { return tablet!; } return mobile; }, ); } }
Navigation with GoRouter
final router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomePage(), routes: [ GoRoute( path: 'details/:id', builder: (context, state) { final id = state.pathParameters['id']!; return DetailsPage(id: id); }, ), ], ), ], );
Theme Configuration
class AppTheme { static ThemeData lightTheme() { return ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.light, ), textTheme: const TextTheme( displayLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), bodyLarge: TextStyle(fontSize: 16), ), ); }
static ThemeData darkTheme() { return ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), ); } }
File Organization
Feature Module Example
features/authentication/ ├── data/ │ ├── datasources/ │ │ ├── auth_local_datasource.dart │ │ └── auth_remote_datasource.dart │ ├── models/ │ │ └── user_model.dart │ └── repositories/ │ └── auth_repository_impl.dart ├── domain/ │ ├── entities/ │ │ └── user.dart │ ├── repositories/ │ │ └── auth_repository.dart │ └── usecases/ │ ├── login.dart │ └── logout.dart └── presentation/ ├── pages/ │ ├── login_page.dart │ └── register_page.dart └── widgets/ ├── login_form.dart └── password_field.dart
Common Tasks
Creating a New Page
-
Create page file in features/[feature]/presentation/pages/
-
Implement as StatelessWidget with proper constructor
-
Extract complex UI into separate widgets
-
Add to router configuration
-
Create tests in corresponding test directory
Adding Dependencies
Add package
flutter pub add package_name
Add dev dependency
flutter pub add --dev package_name
Update dependencies
flutter pub upgrade
Platform-Specific Code
import 'dart:io' show Platform;
if (Platform.isIOS) { // iOS-specific code } else if (Platform.isAndroid) { // Android-specific code }
Using Platform Channels
class PlatformService { static const platform = MethodChannel('com.example.app/channel');
Future<String> getPlatformVersion() async { try { final version = await platform.invokeMethod<String>('getPlatformVersion'); return version ?? 'Unknown'; } on PlatformException catch (e) { return 'Failed to get platform version: ${e.message}'; } } }
Testing Patterns
Widget Tests
testWidgets('MyWidget displays title', (tester) async { await tester.pumpWidget( const MaterialApp( home: MyWidget(title: 'Test Title'), ), );
expect(find.text('Test Title'), findsOneWidget); });
Golden Tests
testWidgets('MyWidget golden test', (tester) async { await tester.pumpWidget( const MaterialApp( home: MyWidget(title: 'Test'), ), );
await expectLater( find.byType(MyWidget), matchesGoldenFile('goldens/my_widget.png'), ); });
Performance Optimization
Keys for Widget Identity
ListView.builder( itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; return ItemWidget( key: ValueKey(item.id), // Preserves widget state item: item, ); }, )
Const Constructors
// Good: Const constructor allows const instances class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.text}); final String text;
@override Widget build(BuildContext context) { return Text(text); } }
// Usage with const const MyWidget(text: 'Hello') // More efficient
Lazy Loading
// Use ListView.builder instead of ListView ListView.builder( itemCount: 1000, itemBuilder: (context, index) => ListTile(title: Text('Item $index')), )
Common Issues and Solutions
Issue: Unbounded Height/Width
Solution: Wrap in Expanded, Flexible, or provide constraints
Column( children: [ Expanded( // Provides bounds child: ListView(...), ), ], )
Issue: RenderFlex Overflow
Solution: Use SingleChildScrollView or adjust layout
SingleChildScrollView( child: Column( children: [...], ), )
Issue: setState Called After Dispose
Solution: Check if mounted before setState
if (mounted) { setState(() { // Update state }); }
Flutter Commands Reference
Create new project
flutter create my_app
Run app
flutter run
Run on specific device
flutter run -d chrome flutter run -d macos
Build release
flutter build apk flutter build ipa flutter build web
Analyze code
flutter analyze
Run tests
flutter test
Format code
dart format lib/
Clean build
flutter clean
Doctor check
flutter doctor -v
Best Practices Checklist
-
Use const constructors for immutable widgets
-
Separate business logic from UI
-
Implement proper error handling
-
Add meaningful widget keys where needed
-
Write widget tests for critical UI
-
Follow Flutter style guide
-
Use meaningful variable and class names
-
Document complex widgets and logic
-
Handle loading and error states
-
Optimize images and assets
-
Use appropriate state management
-
Implement responsive layouts
-
Test on multiple screen sizes
-
Profile performance before optimizing
Resources
-
Official Docs: https://docs.flutter.dev
-
Widget Catalog: https://docs.flutter.dev/ui/widgets
-
Cookbook: https://docs.flutter.dev/cookbook
-
API Reference: https://api.flutter.dev
-
Performance Best Practices: https://docs.flutter.dev/perf/best-practices
Notes
This skill provides Flutter-agnostic guidance. For state management specifics, use the dedicated Riverpod or Bloc skills. This skill focuses on Flutter SDK features, widget composition, layout, navigation, and platform integration.