# Flutter AI Rules - Reglas y Mejores Prácticas ## Principios Fundamentales ### Arquitectura Limpia - Separar lógica de negocio de la UI - Usar BLoC/Cubit para gestión de estado - Implementar Repository Pattern para datos - Aplicar Dependency Injection ### Performance y Optimización - Usar `const` constructors siempre que sea posible - Implementar `keys` en widgets dinámicos - Optimizar rebuilds con `Builder` widgets - Usar `ListView.builder` para listas largas ## Estructura de Proyecto Recomendada ``` lib/ ├── main.dart ├── app/ │ ├── app.dart │ ├── routes/ │ └── theme/ ├── core/ │ ├── constants/ │ ├── errors/ │ ├── services/ │ └── utils/ ├── features/ │ ├── authentication/ │ │ ├── data/ │ │ ├── domain/ │ │ └── presentation/ │ └── home/ │ ├── data/ │ ├── domain/ │ └── presentation/ └── shared/ ├── widgets/ ├── models/ └── services/ ``` ## Gestión de Estado con BLoC ### Estructura de BLoC ```dart // Events abstract class UserEvent extends Equatable { const UserEvent(); } class LoadUser extends UserEvent { final String userId; const LoadUser(this.userId); @override List get props => [userId]; } // States abstract class UserState extends Equatable { const UserState(); } class UserInitial extends UserState { @override List get props => []; } class UserLoading extends UserState { @override List get props => []; } class UserLoaded extends UserState { final User user; const UserLoaded(this.user); @override List get props => [user]; } class UserError extends UserState { final String message; const UserError(this.message); @override List get props => [message]; } // BLoC class UserBloc extends Bloc { final UserRepository userRepository; UserBloc({required this.userRepository}) : super(UserInitial()) { on(_onLoadUser); } Future _onLoadUser(LoadUser event, Emitter emit) async { emit(UserLoading()); try { final user = await userRepository.getUser(event.userId); emit(UserLoaded(user)); } catch (e) { emit(UserError(e.toString())); } } } ``` ## Widgets y UI ### Widget Composition ```dart // ✅ Bueno - Composition over inheritance class UserCard extends StatelessWidget { const UserCard({ Key? key, required this.user, this.onTap, }) : super(key: key); final User user; final VoidCallback? onTap; @override Widget build(BuildContext context) { return Card( child: ListTile( leading: UserAvatar(user: user), title: Text(user.name), subtitle: Text(user.email), onTap: onTap, ), ); } } // ✅ Bueno - Const constructor class UserAvatar extends StatelessWidget { const UserAvatar({ Key? key, required this.user, this.radius = 20, }) : super(key: key); final User user; final double radius; @override Widget build(BuildContext context) { return CircleAvatar( radius: radius, backgroundImage: user.avatarUrl != null ? NetworkImage(user.avatarUrl!) : null, child: user.avatarUrl == null ? Text(user.name.substring(0, 1).toUpperCase()) : null, ); } } ``` ### Responsive Design ```dart class ResponsiveLayout extends StatelessWidget { const ResponsiveLayout({ Key? key, required this.mobile, required this.tablet, required this.desktop, }) : super(key: key); final Widget mobile; final Widget tablet; final Widget desktop; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth < 768) { return mobile; } else if (constraints.maxWidth < 1024) { return tablet; } else { return desktop; } }, ); } } ``` ## Manejo de Datos ### Repository Pattern ```dart abstract class UserRepository { Future getUser(String id); Future> getUsers(); Future createUser(User user); Future updateUser(User user); Future deleteUser(String id); } class UserRepositoryImpl implements UserRepository { final UserDataSource remoteDataSource; final UserDataSource localDataSource; const UserRepositoryImpl({ required this.remoteDataSource, required this.localDataSource, }); @override Future getUser(String id) async { try { final user = await remoteDataSource.getUser(id); await localDataSource.cacheUser(user); return user; } catch (e) { return await localDataSource.getUser(id); } } } ``` ### Data Models ```dart import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; part 'user.g.dart'; @JsonSerializable() class User extends Equatable { const User({ required this.id, required this.name, required this.email, this.avatarUrl, required this.createdAt, }); final String id; final String name; final String email; final String? avatarUrl; final DateTime createdAt; factory User.fromJson(Map json) => _$UserFromJson(json); Map toJson() => _$UserToJson(this); User copyWith({ String? id, String? name, String? email, String? avatarUrl, DateTime? createdAt, }) { return User( id: id ?? this.id, name: name ?? this.name, email: email ?? this.email, avatarUrl: avatarUrl ?? this.avatarUrl, createdAt: createdAt ?? this.createdAt, ); } @override List get props => [id, name, email, avatarUrl, createdAt]; } ``` ## Testing ### Unit Tests ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:bloc_test/bloc_test.dart'; class MockUserRepository extends Mock implements UserRepository {} void main() { group('UserBloc', () { late UserBloc userBloc; late MockUserRepository mockUserRepository; setUp(() { mockUserRepository = MockUserRepository(); userBloc = UserBloc(userRepository: mockUserRepository); }); tearDown(() { userBloc.close(); }); test('initial state is UserInitial', () { expect(userBloc.state, equals(UserInitial())); }); blocTest( 'emits [UserLoading, UserLoaded] when LoadUser is added', build: () { when(mockUserRepository.getUser(any)) .thenAnswer((_) async => testUser); return userBloc; }, act: (bloc) => bloc.add(LoadUser('1')), expect: () => [ UserLoading(), UserLoaded(testUser), ], ); }); } ``` ### Widget Tests ```dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('UserCard Widget', () { testWidgets('displays user information correctly', (tester) async { const user = User( id: '1', name: 'John Doe', email: 'john@example.com', createdAt: DateTime.now(), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: UserCard(user: user), ), ), ); expect(find.text('John Doe'), findsOneWidget); expect(find.text('john@example.com'), findsOneWidget); }); testWidgets('calls onTap when tapped', (tester) async { bool wasTapped = false; await tester.pumpWidget( MaterialApp( home: Scaffold( body: UserCard( user: testUser, onTap: () => wasTapped = true, ), ), ), ); await tester.tap(find.byType(UserCard)); expect(wasTapped, isTrue); }); }); } ``` ## Error Handling ### Failure Classes ```dart abstract class Failure extends Equatable { const Failure(); } class ServerFailure extends Failure { final String message; const ServerFailure(this.message); @override List get props => [message]; } class NetworkFailure extends Failure { @override List get props => []; } class CacheFailure extends Failure { @override List get props => []; } ``` ### Result Type ```dart import 'package:dartz/dartz.dart'; typedef Result = Either; class UserService { final UserRepository repository; const UserService(this.repository); Future> getUser(String id) async { try { final user = await repository.getUser(id); return Right(user); } on ServerException catch (e) { return Left(ServerFailure(e.message)); } on NetworkException { return Left(NetworkFailure()); } } } ``` ## Dependency Injection ### GetIt Setup ```dart import 'package:get_it/get_it.dart'; import 'package:dio/dio.dart'; final getIt = GetIt.instance; Future configureDependencies() async { // External getIt.registerLazySingleton(() => Dio()); // Data sources getIt.registerLazySingleton( () => UserRemoteDataSource(getIt()), ); // Repositories getIt.registerLazySingleton( () => UserRepositoryImpl( remoteDataSource: getIt(), localDataSource: getIt(), ), ); // BLoCs getIt.registerFactory( () => UserBloc(userRepository: getIt()), ); } ``` ## Performance Optimizations ### Const Constructors ```dart // ✅ Bueno - Const constructor class AppTitle extends StatelessWidget { const AppTitle({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Text('My App'); } } // ✅ Bueno - Const widget usage Widget build(BuildContext context) { return Column( children: const [ AppTitle(), SizedBox(height: 16), ], ); } ``` ### ListView Optimization ```dart // ✅ Bueno - ListView.builder para listas largas class UserList extends StatelessWidget { const UserList({Key? key, required this.users}) : super(key: key); final List users; @override Widget build(BuildContext context) { return ListView.builder( itemCount: users.length, itemBuilder: (context, index) { return UserCard( key: ValueKey(users[index].id), user: users[index], ); }, ); } } ``` ## Security Best Practices ### Secure Storage ```dart import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SecureStorageService { static const _storage = FlutterSecureStorage(); static Future storeToken(String token) async { await _storage.write(key: 'auth_token', value: token); } static Future getToken() async { return await _storage.read(key: 'auth_token'); } static Future deleteToken() async { await _storage.delete(key: 'auth_token'); } } ``` ### API Security ```dart class ApiInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { // Add auth token final token = SecureStorageService.getToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } // Add security headers options.headers['Content-Type'] = 'application/json'; options.headers['Accept'] = 'application/json'; super.onRequest(options, handler); } } ```