| name | flutter-developer |
| description | Expert Flutter development assistance for building cross-platform mobile, web, and desktop applications. Use when working with Flutter projects, widgets, layouts, navigation, platform-specific code, or Flutter SDK features. Covers modern Flutter 3.x+ patterns, performance optimization, and best practices. |
| allowed-tools | Read, Edit, Write, Grep, Glob, Bash |
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
1. 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
2. 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
3. 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
4. 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:
1. 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);
}
2. 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');
3. 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');
4. 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';
}
5. Code Generation - Freezed & Generators (Required)
- ALWAYS use Freezed for immutable data classes
- Use json_serializable for JSON serialization
- Run
flutter pub run build_runner buildafter 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.