--- name: Flutter Expert description: Expert in Flutter for building cross-platform applications. Use when working with Flutter, Dart, widgets, state management (Provider, Riverpod, Bloc), Material Design, Cupertino, or when the user mentions Flutter, Dart, mobile development, or cross-platform apps. --- # Flutter Expert A specialized skill for building high-performance cross-platform applications with Flutter and Dart. ## Instructions ### Core Workflow 1. **Understand requirements** - Identify platforms (iOS, Android, Web, Desktop) - Determine state management approach - Understand UI/UX requirements - Identify native integrations needed 2. **Project structure** - Organize with feature-based architecture - Implement proper state management - Set up routing and navigation - Configure for multiple platforms 3. **Implement features** - Create reusable widgets - Implement responsive layouts - Handle platform-specific code - Optimize performance 4. **Testing and deployment** - Write widget, integration, and unit tests - Optimize app size and performance - Configure platform-specific builds ### Flutter Project Structure ``` lib/ ├── main.dart ├── app.dart ├── core/ │ ├── constants/ │ ├── themes/ │ ├── utils/ │ └── extensions/ ├── features/ │ ├── auth/ │ │ ├── data/ │ │ ├── domain/ │ │ ├── presentation/ │ │ └── providers/ │ └── home/ ├── shared/ │ ├── widgets/ │ ├── models/ │ └── services/ └── routes/ ``` ### Widget Best Practices ```dart import 'package:flutter/material.dart'; // Stateless Widget class CustomButton extends StatelessWidget { final String label; final VoidCallback onPressed; final bool isLoading; const CustomButton({ Key? key, required this.label, required this.onPressed, this.isLoading = false, }) : super(key: key); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: isLoading ? null : onPressed, child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : Text(label), ); } } // Stateful Widget class Counter extends StatefulWidget { const Counter({Key? key}) : super(key: key); @override State createState() => _CounterState(); } class _CounterState extends State { int _count = 0; void _increment() { setState(() { _count++; }); } @override Widget build(BuildContext context) { return Column( children: [ Text('Count: $_count'), ElevatedButton( onPressed: _increment, child: const Text('Increment'), ), ], ); } } ``` ### State Management (Riverpod) ```dart import 'package:flutter_riverpod/flutter_riverpod.dart'; // Provider final counterProvider = StateProvider((ref) => 0); // FutureProvider final userProvider = FutureProvider((ref) async { final response = await http.get(Uri.parse('/api/user')); return User.fromJson(jsonDecode(response.body)); }); // StateNotifier class TodosNotifier extends StateNotifier> { TodosNotifier() : super([]); void addTodo(Todo todo) { state = [...state, todo]; } void removeTodo(String id) { state = state.where((todo) => todo.id != id).toList(); } void toggleTodo(String id) { state = [ for (final todo in state) if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo, ]; } } final todosProvider = StateNotifierProvider>((ref) { return TodosNotifier(); }); // Using in Widget class TodoList extends ConsumerWidget { const TodoList({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final todos = ref.watch(todosProvider); return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( title: Text(todo.title), leading: Checkbox( value: todo.completed, onChanged: (_) { ref.read(todosProvider.notifier).toggleTodo(todo.id); }, ), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () { ref.read(todosProvider.notifier).removeTodo(todo.id); }, ), ); }, ); } } ``` ### Responsive Design ```dart class ResponsiveLayout extends StatelessWidget { final Widget mobile; final Widget? tablet; final Widget desktop; const ResponsiveLayout({ Key? key, required this.mobile, this.tablet, required this.desktop, }) : super(key: key); static bool isMobile(BuildContext context) => MediaQuery.of(context).size.width < 650; static bool isTablet(BuildContext context) => MediaQuery.of(context).size.width >= 650 && MediaQuery.of(context).size.width < 1100; static bool isDesktop(BuildContext context) => MediaQuery.of(context).size.width >= 1100; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth >= 1100) { return desktop; } else if (constraints.maxWidth >= 650) { return tablet ?? mobile; } else { return mobile; } }, ); } } ``` ### Navigation (GoRouter) ```dart import 'package:go_router/go_router.dart'; final goRouter = GoRouter( initialLocation: '/', routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/details/:id', builder: (context, state) { final id = state.pathParameters['id']!; return DetailsScreen(id: id); }, ), GoRoute( path: '/profile', builder: (context, state) => const ProfileScreen(), ), ], redirect: (context, state) { // Add auth guard logic here final isLoggedIn = false; // Check auth state if (!isLoggedIn && state.location != '/login') { return '/login'; } return null; }, ); // In main.dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: goRouter, theme: ThemeData(primarySwatch: Colors.blue), ); } } // Navigate context.go('/details/123'); context.push('/profile'); ``` ### API Integration ```dart import 'package:http/http.dart' as http; import 'dart:convert'; class ApiService { static const baseUrl = 'https://api.example.com'; Future> getUsers() async { final response = await http.get(Uri.parse('$baseUrl/users')); if (response.statusCode == 200) { final List data = jsonDecode(response.body); return data.map((json) => User.fromJson(json)).toList(); } else { throw Exception('Failed to load users'); } } Future createUser(User user) async { final response = await http.post( Uri.parse('$baseUrl/users'), headers: {'Content-Type': 'application/json'}, body: jsonEncode(user.toJson()), ); if (response.statusCode == 201) { return User.fromJson(jsonDecode(response.body)); } else { throw Exception('Failed to create user'); } } } // With Riverpod final apiServiceProvider = Provider((ref) => ApiService()); final usersProvider = FutureProvider>((ref) async { final apiService = ref.read(apiServiceProvider); return apiService.getUsers(); }); ``` ### Testing ```dart // Widget Test void main() { testWidgets('Counter increments', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: Counter())); expect(find.text('Count: 0'), findsOneWidget); await tester.tap(find.text('Increment')); await tester.pump(); expect(find.text('Count: 1'), findsOneWidget); }); // Unit Test test('TodosNotifier adds todo', () { final notifier = TodosNotifier(); final todo = Todo(id: '1', title: 'Test', completed: false); notifier.addTodo(todo); expect(notifier.state.length, 1); expect(notifier.state.first.title, 'Test'); }); // Integration Test testWidgets('Full app test', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); expect(find.text('Home'), findsOneWidget); await tester.tap(find.text('Go to Profile')); await tester.pumpAndSettle(); expect(find.text('Profile'), findsOneWidget); }); } ``` ## Critical Rules ### Always Do - Use const constructors when possible - Implement proper keys for lists - Handle async operations properly - Use BuildContext appropriately - Implement error handling - Test on all target platforms - Optimize widget rebuilds - Use proper state management - Follow Material/Cupertino guidelines - Profile performance ### Never Do - Never build widgets in build method - Never mutate state directly - Never ignore platform differences - Never skip const constructors - Never create unnecessary StatefulWidgets - Never ignore memory leaks - Never block the UI thread - Never hardcode sizes ## Knowledge Base - **Flutter Core**: Widgets, State Management, Navigation - **Dart**: Language features, async/await, null safety - **State Management**: Provider, Riverpod, Bloc, GetX - **UI**: Material Design, Cupertino, Custom painters - **Platform**: iOS, Android, Web, Desktop - **Testing**: Widget tests, Integration tests, Unit tests - **Performance**: Profiling, optimization techniques - **Native**: Platform channels, method channels ## Integration with Other Skills - **Works with**: Fullstack Guardian, Test Master - **Complements**: React Native Expert (alternative mobile) ## Best Practices Summary 1. **Widgets**: Use const, proper keys, avoid rebuilds 2. **State**: Choose appropriate state management 3. **Performance**: Profile and optimize 4. **Platform**: Handle platform differences 5. **Testing**: Comprehensive test coverage 6. **Navigation**: Type-safe routing 7. **API**: Proper error handling 8. **UI**: Follow platform guidelines 9. **Code**: Clean, maintainable, documented 10. **Build**: Optimize app size