Claude-skill-registry feature-first

🎨 Skill: Feature-First Architecture

install
source Β· Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code Β· Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/feature-first" ~/.claude/skills/majiayu000-claude-skill-registry-feature-first && rm -rf "$T"
manifest: skills/data/feature-first/SKILL.md
source content

🎨 Skill: Feature-First Architecture

πŸ“‹ Metadata

AtributoValor
ID
flutter-feature-first
Nivel🟑 Intermedio
VersiΓ³n1.0.0
Keywords
feature-first
,
feature-architecture
,
feature-driven
ReferenciaFeature-First Architecture Guide

πŸ”‘ Keywords para InvocaciΓ³n

Usa cualquiera de estos keywords en tus prompts para invocar este skill:

  • feature-first
  • feature-architecture
  • feature-driven
  • @skill:feature-first

Ejemplos de Prompts

Crea una app con feature-first architecture
Organiza el proyecto con estructura feature-first
@skill:feature-first - Estructura la app por features

πŸ“– DescripciΓ³n

⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio

mobile/
). El skill incluye verificaciones para asegurar que se estΓ‘ en el directorio correcto antes de ejecutar cualquier comando.

Feature-First Architecture organiza el cΓ³digo por features en lugar de por capas tΓ©cnicas. Cada feature contiene todo lo necesario (UI, lΓ³gica, datos) en una carpeta auto-contenida, facilitando la navegaciΓ³n y el mantenimiento del cΓ³digo.

βœ… CuΓ‘ndo Usar Este Skill

  • Proyectos medianos a grandes con mΓΊltiples features
  • Equipos que trabajan en features especΓ­ficas
  • Necesitas navegaciΓ³n rΓ‘pida en el cΓ³digo
  • Quieres features auto-contenidas y cohesivas
  • Prefieres organizaciΓ³n por dominio de negocio
  • Necesitas escalar la app agregando features
  • Quieres reducir conflictos de merge en el equipo

❌ CuÑndo NO Usar Este Skill

  • Proyectos muy pequeΓ±os (1-2 pantallas)
  • Aplicaciones con pocas features
  • Prefieres organizaciΓ³n por capas tΓ©cnicas (Data/Domain/Presentation)

πŸ—οΈ Estructura del Proyecto

lib/
β”œβ”€β”€ core/
β”‚   β”œβ”€β”€ constants/
β”‚   β”‚   β”œβ”€β”€ app_constants.dart
β”‚   β”‚   └── api_endpoints.dart
β”‚   β”œβ”€β”€ theme/
β”‚   β”‚   β”œβ”€β”€ app_theme.dart
β”‚   β”‚   β”œβ”€β”€ app_colors.dart
β”‚   β”‚   └── app_text_styles.dart
β”‚   β”œβ”€β”€ widgets/
β”‚   β”‚   β”œβ”€β”€ buttons/
β”‚   β”‚   β”‚   β”œβ”€β”€ primary_button.dart
β”‚   β”‚   β”‚   └── secondary_button.dart
β”‚   β”‚   β”œβ”€β”€ inputs/
β”‚   β”‚   β”‚   β”œβ”€β”€ text_field.dart
β”‚   β”‚   β”‚   └── search_field.dart
β”‚   β”‚   └── loading/
β”‚   β”‚       └── loading_indicator.dart
β”‚   β”œβ”€β”€ router/
β”‚   β”‚   β”œβ”€β”€ app_router.dart
β”‚   β”‚   └── routes.dart
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ api_service.dart
β”‚   β”‚   β”œβ”€β”€ storage_service.dart
β”‚   β”‚   └── analytics_service.dart
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”œβ”€β”€ validators.dart
β”‚   β”‚   β”œβ”€β”€ formatters.dart
β”‚   β”‚   └── extensions/
β”‚   β”‚       β”œβ”€β”€ string_extensions.dart
β”‚   β”‚       β”œβ”€β”€ date_extensions.dart
β”‚   β”‚       └── context_extensions.dart
β”‚   └── error/
β”‚       β”œβ”€β”€ failures.dart
β”‚       └── exceptions.dart
β”‚
β”œβ”€β”€ features/
β”‚   β”œβ”€β”€ authentication/
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_local_datasource.dart
β”‚   β”‚   β”‚   β”‚   └── auth_remote_datasource.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ user_model.dart
β”‚   β”‚   β”‚   β”‚   └── token_model.dart
β”‚   β”‚   β”‚   └── repositories/
β”‚   β”‚   β”‚       └── auth_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”‚   β”‚   └── user.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   β”‚   β”‚   └── auth_repository.dart
β”‚   β”‚   β”‚   └── usecases/
β”‚   β”‚   β”‚       β”œβ”€β”€ login_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ register_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ logout_usecase.dart
β”‚   β”‚   β”‚       └── get_user_usecase.dart
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   β”œβ”€β”€ bloc/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_bloc.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_event.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth_state.dart
β”‚   β”‚   β”‚   β”‚   └── login/
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ login_cubit.dart
β”‚   β”‚   β”‚   β”‚       └── login_state.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login_screen.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ register_screen.dart
β”‚   β”‚   β”‚   β”‚   └── forgot_password_screen.dart
β”‚   β”‚   β”‚   └── widgets/
β”‚   β”‚   β”‚       β”œβ”€β”€ login_form.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ register_form.dart
β”‚   β”‚   β”‚       └── social_login_buttons.dart
β”‚   β”‚   └── authentication.dart  // Barrel file
β”‚   β”‚
β”‚   β”œβ”€β”€ products/
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ products_local_datasource.dart
β”‚   β”‚   β”‚   β”‚   └── products_remote_datasource.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ product_model.dart
β”‚   β”‚   β”‚   β”‚   └── category_model.dart
β”‚   β”‚   β”‚   └── repositories/
β”‚   β”‚   β”‚       └── products_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ product.dart
β”‚   β”‚   β”‚   β”‚   └── category.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   β”‚   β”‚   └── products_repository.dart
β”‚   β”‚   β”‚   └── usecases/
β”‚   β”‚   β”‚       β”œβ”€β”€ get_products_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ get_product_detail_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ search_products_usecase.dart
β”‚   β”‚   β”‚       └── filter_products_usecase.dart
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   β”œβ”€β”€ bloc/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ products_bloc.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ products_event.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ products_state.dart
β”‚   β”‚   β”‚   β”‚   └── product_detail/
β”‚   β”‚   β”‚   β”‚       β”œβ”€β”€ product_detail_cubit.dart
β”‚   β”‚   β”‚   β”‚       └── product_detail_state.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ products_screen.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ product_detail_screen.dart
β”‚   β”‚   β”‚   β”‚   └── search_screen.dart
β”‚   β”‚   β”‚   └── widgets/
β”‚   β”‚   β”‚       β”œβ”€β”€ product_card.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ product_grid.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ category_filter.dart
β”‚   β”‚   β”‚       └── price_filter.dart
β”‚   β”‚   └── products.dart  // Barrel file
β”‚   β”‚
β”‚   β”œβ”€β”€ cart/
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   β”œβ”€β”€ datasources/
β”‚   β”‚   β”‚   β”‚   └── cart_local_datasource.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”‚   └── cart_item_model.dart
β”‚   β”‚   β”‚   └── repositories/
β”‚   β”‚   β”‚       └── cart_repository_impl.dart
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ entities/
β”‚   β”‚   β”‚   β”‚   └── cart_item.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   β”‚   β”‚   └── cart_repository.dart
β”‚   β”‚   β”‚   └── usecases/
β”‚   β”‚   β”‚       β”œβ”€β”€ add_to_cart_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ remove_from_cart_usecase.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ update_quantity_usecase.dart
β”‚   β”‚   β”‚       └── get_cart_items_usecase.dart
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”‚   β”œβ”€β”€ bloc/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ cart_bloc.dart
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ cart_event.dart
β”‚   β”‚   β”‚   β”‚   └── cart_state.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”‚   β”‚   └── cart_screen.dart
β”‚   β”‚   β”‚   └── widgets/
β”‚   β”‚   β”‚       β”œβ”€β”€ cart_item_card.dart
β”‚   β”‚   β”‚       β”œβ”€β”€ cart_summary.dart
β”‚   β”‚   β”‚       └── empty_cart.dart
β”‚   β”‚   └── cart.dart  // Barrel file
β”‚   β”‚
β”‚   β”œβ”€β”€ orders/
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   └── orders.dart
β”‚   β”‚
β”‚   β”œβ”€β”€ profile/
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   └── profile.dart
β”‚   β”‚
β”‚   └── settings/
β”‚       β”œβ”€β”€ data/
β”‚       β”œβ”€β”€ domain/
β”‚       β”œβ”€β”€ presentation/
β”‚       └── settings.dart
β”‚
└── main.dart

πŸ“¦ Dependencias Requeridas

dependencies:
  flutter:
    sdk: flutter
  
  # State Management
  flutter_bloc: ^8.1.3
  equatable: ^2.0.5
  
  # Navigation
  go_router: ^12.1.3
  
  # Dependency Injection
  get_it: ^7.6.4
  injectable: ^2.3.2
  
  # Networking
  dio: ^5.4.0
  retrofit: ^4.0.3
  
  # Local Storage
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  
  # Utils
  dartz: ^0.10.1
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1

dev_dependencies:
  # Code Generation
  build_runner: ^2.4.6
  freezed: ^2.4.5
  json_serializable: ^6.7.1
  injectable_generator: ^2.4.1
  retrofit_generator: ^8.0.6
  hive_generator: ^2.0.1
  
  # Testing
  flutter_test:
    sdk: flutter
  bloc_test: ^9.1.4
  mocktail: ^1.0.1

πŸ’» ImplementaciΓ³n

1. Core - ConfiguraciΓ³n de Router

// lib/core/router/app_router.dart
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
import '../../features/authentication/authentication.dart';
import '../../features/products/products.dart';
import '../../features/cart/cart.dart';
import '../../features/orders/orders.dart';
import '../../features/profile/profile.dart';

final appRouter = GoRouter(
  initialLocation: '/login',
  routes: [
    // Authentication Routes
    GoRoute(
      path: '/login',
      name: 'login',
      builder: (context, state) => const LoginScreen(),
    ),
    GoRoute(
      path: '/register',
      name: 'register',
      builder: (context, state) => const RegisterScreen(),
    ),
    
    // Main App with Bottom Navigation
    ShellRoute(
      builder: (context, state, child) {
        return MainScaffold(child: child);
      },
      routes: [
        // Products Routes
        GoRoute(
          path: '/products',
          name: 'products',
          builder: (context, state) => const ProductsScreen(),
          routes: [
            GoRoute(
              path: ':id',
              name: 'product-detail',
              builder: (context, state) {
                final productId = state.pathParameters['id']!;
                return ProductDetailScreen(productId: productId);
              },
            ),
          ],
        ),
        
        // Cart Routes
        GoRoute(
          path: '/cart',
          name: 'cart',
          builder: (context, state) => const CartScreen(),
        ),
        
        // Orders Routes
        GoRoute(
          path: '/orders',
          name: 'orders',
          builder: (context, state) => const OrdersScreen(),
          routes: [
            GoRoute(
              path: ':id',
              name: 'order-detail',
              builder: (context, state) {
                final orderId = state.pathParameters['id']!;
                return OrderDetailScreen(orderId: orderId);
              },
            ),
          ],
        ),
        
        // Profile Routes
        GoRoute(
          path: '/profile',
          name: 'profile',
          builder: (context, state) => const ProfileScreen(),
        ),
      ],
    ),
  ],
  redirect: (context, state) {
    // Implementar lΓ³gica de autenticaciΓ³n aquΓ­
    // final isAuthenticated = ...
    // if (!isAuthenticated && state.location != '/login') {
    //   return '/login';
    // }
    return null;
  },
);

2. Dependency Injection

// lib/core/di/injection.dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';

final getIt = GetIt.instance;

@InjectableInit()
Future<void> configureDependencies() async {
  await getIt.init();
}
// lib/core/di/injection.config.dart (generado)
// Ejecutar desde la raΓ­z del proyecto:
// cd mobile && dart run build_runner build --delete-conflicting-outputs && cd ..

3. Feature: Authentication

Domain Layer

// lib/features/authentication/domain/entities/user.dart
import 'package:equatable/equatable.dart';

class User extends Equatable {
  final String id;
  final String email;
  final String name;
  final String? avatar;

  const User({
    required this.id,
    required this.email,
    required this.name,
    this.avatar,
  });

  @override
  List<Object?> get props => [id, email, name, avatar];
}
// lib/features/authentication/domain/repositories/auth_repository.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/user.dart';

abstract class AuthRepository {
  Future<Either<Failure, User>> login({
    required String email,
    required String password,
  });
  
  Future<Either<Failure, User>> register({
    required String email,
    required String password,
    required String name,
  });
  
  Future<Either<Failure, void>> logout();
  
  Future<Either<Failure, User>> getCurrentUser();
}
// lib/features/authentication/domain/usecases/login_usecase.dart
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/failures.dart';
import '../entities/user.dart';
import '../repositories/auth_repository.dart';

@injectable
class LoginUseCase {
  final AuthRepository repository;

  LoginUseCase(this.repository);

  Future<Either<Failure, User>> call({
    required String email,
    required String password,
  }) async {
    return await repository.login(email: email, password: password);
  }
}

Data Layer

// lib/features/authentication/data/models/user_model.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/entities/user.dart';

part 'user_model.freezed.dart';
part 'user_model.g.dart';

@freezed
class UserModel with _$UserModel {
  const UserModel._();
  
  const factory UserModel({
    required String id,
    required String email,
    required String name,
    String? avatar,
  }) = _UserModel;

  factory UserModel.fromJson(Map<String, dynamic> json) =>
      _$UserModelFromJson(json);

  // Convert to domain entity
  User toEntity() {
    return User(
      id: id,
      email: email,
      name: name,
      avatar: avatar,
    );
  }

  // Convert from domain entity
  factory UserModel.fromEntity(User user) {
    return UserModel(
      id: user.id,
      email: user.email,
      name: user.name,
      avatar: user.avatar,
    );
  }
}
// lib/features/authentication/data/datasources/auth_remote_datasource.dart
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../models/user_model.dart';

abstract class AuthRemoteDataSource {
  Future<UserModel> login({required String email, required String password});
  Future<UserModel> register({required String email, required String password, required String name});
  Future<void> logout();
  Future<UserModel> getCurrentUser();
}

@LazySingleton(as: AuthRemoteDataSource)
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
  final Dio dio;

  AuthRemoteDataSourceImpl(this.dio);

  @override
  Future<UserModel> login({
    required String email,
    required String password,
  }) async {
    try {
      final response = await dio.post(
        '/auth/login',
        data: {
          'email': email,
          'password': password,
        },
      );

      return UserModel.fromJson(response.data['user']);
    } catch (e) {
      throw Exception('Login failed: $e');
    }
  }

  @override
  Future<UserModel> register({
    required String email,
    required String password,
    required String name,
  }) async {
    try {
      final response = await dio.post(
        '/auth/register',
        data: {
          'email': email,
          'password': password,
          'name': name,
        },
      );

      return UserModel.fromJson(response.data['user']);
    } catch (e) {
      throw Exception('Registration failed: $e');
    }
  }

  @override
  Future<void> logout() async {
    try {
      await dio.post('/auth/logout');
    } catch (e) {
      throw Exception('Logout failed: $e');
    }
  }

  @override
  Future<UserModel> getCurrentUser() async {
    try {
      final response = await dio.get('/auth/user');
      return UserModel.fromJson(response.data['user']);
    } catch (e) {
      throw Exception('Get current user failed: $e');
    }
  }
}
// lib/features/authentication/data/repositories/auth_repository_impl.dart
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/failures.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/auth_remote_datasource.dart';

@LazySingleton(as: AuthRepository)
class AuthRepositoryImpl implements AuthRepository {
  final AuthRemoteDataSource remoteDataSource;

  AuthRepositoryImpl(this.remoteDataSource);

  @override
  Future<Either<Failure, User>> login({
    required String email,
    required String password,
  }) async {
    try {
      final userModel = await remoteDataSource.login(
        email: email,
        password: password,
      );
      return Right(userModel.toEntity());
    } catch (e) {
      return Left(ServerFailure(e.toString()));
    }
  }

  @override
  Future<Either<Failure, User>> register({
    required String email,
    required String password,
    required String name,
  }) async {
    try {
      final userModel = await remoteDataSource.register(
        email: email,
        password: password,
        name: name,
      );
      return Right(userModel.toEntity());
    } catch (e) {
      return Left(ServerFailure(e.toString()));
    }
  }

  @override
  Future<Either<Failure, void>> logout() async {
    try {
      await remoteDataSource.logout();
      return const Right(null);
    } catch (e) {
      return Left(ServerFailure(e.toString()));
    }
  }

  @override
  Future<Either<Failure, User>> getCurrentUser() async {
    try {
      final userModel = await remoteDataSource.getCurrentUser();
      return Right(userModel.toEntity());
    } catch (e) {
      return Left(ServerFailure(e.toString()));
    }
  }
}

Presentation Layer

// lib/features/authentication/presentation/bloc/login/login_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/usecases/login_usecase.dart';

part 'login_state.dart';
part 'login_cubit.freezed.dart';

@injectable
class LoginCubit extends Cubit<LoginState> {
  final LoginUseCase loginUseCase;

  LoginCubit(this.loginUseCase) : super(const LoginState.initial());

  Future<void> login({
    required String email,
    required String password,
  }) async {
    emit(const LoginState.loading());

    final result = await loginUseCase(email: email, password: password);

    result.fold(
      (failure) => emit(LoginState.error(failure.message)),
      (user) => emit(LoginState.success(user)),
    );
  }
}
// lib/features/authentication/presentation/bloc/login/login_state.dart
part of 'login_cubit.dart';

@freezed
class LoginState with _$LoginState {
  const factory LoginState.initial() = LoginInitial;
  const factory LoginState.loading() = LoginLoading;
  const factory LoginState.success(User user) = LoginSuccess;
  const factory LoginState.error(String message) = LoginError;
}
// lib/features/authentication/presentation/screens/login_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/widgets/buttons/primary_button.dart';
import '../../../../core/theme/app_colors.dart';
import '../bloc/login/login_cubit.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => getIt<LoginCubit>(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Login'),
        ),
        body: BlocConsumer<LoginCubit, LoginState>(
          listener: (context, state) {
            state.maybeWhen(
              success: (user) {
                context.go('/products');
              },
              error: (message) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text(message),
                    backgroundColor: AppColors.error,
                  ),
                );
              },
              orElse: () {},
            );
          },
          builder: (context, state) {
            final isLoading = state is LoginLoading;

            return Padding(
              padding: const EdgeInsets.all(24.0),
              child: Form(
                key: _formKey,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    TextFormField(
                      controller: _emailController,
                      decoration: const InputDecoration(
                        labelText: 'Email',
                        prefixIcon: Icon(Icons.email),
                      ),
                      keyboardType: TextInputType.emailAddress,
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'Please enter your email';
                        }
                        return null;
                      },
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: _passwordController,
                      decoration: const InputDecoration(
                        labelText: 'Password',
                        prefixIcon: Icon(Icons.lock),
                      ),
                      obscureText: true,
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'Please enter your password';
                        }
                        return null;
                      },
                    ),
                    const SizedBox(height: 24),
                    SizedBox(
                      width: double.infinity,
                      child: PrimaryButton(
                        text: 'Login',
                        isLoading: isLoading,
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            context.read<LoginCubit>().login(
                                  email: _emailController.text,
                                  password: _passwordController.text,
                                );
                          }
                        },
                      ),
                    ),
                    const SizedBox(height: 16),
                    TextButton(
                      onPressed: () => context.push('/register'),
                      child: const Text('Don\'t have an account? Register'),
                    ),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

Barrel File

// lib/features/authentication/authentication.dart
// Domain
export 'domain/entities/user.dart';
export 'domain/repositories/auth_repository.dart';
export 'domain/usecases/login_usecase.dart';
export 'domain/usecases/register_usecase.dart';
export 'domain/usecases/logout_usecase.dart';
export 'domain/usecases/get_user_usecase.dart';

// Presentation
export 'presentation/screens/login_screen.dart';
export 'presentation/screens/register_screen.dart';
export 'presentation/bloc/auth_bloc.dart';
export 'presentation/bloc/login/login_cubit.dart';

4. Main Setup

// lib/main.dart
import 'package:flutter/material.dart';
import 'core/di/injection.dart';
import 'core/router/app_router.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure dependency injection
  await configureDependencies();
  
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Feature-First App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      routerConfig: appRouter,
    );
  }
}

🎯 Mejores PrÑcticas

1. OrganizaciΓ³n por Feature

βœ… DO:

features/
  authentication/
    data/
    domain/
    presentation/
    authentication.dart

❌ DON'T:

data/
  authentication/
domain/
  authentication/
presentation/
  authentication/

2. Barrel Files

βœ… DO:

// lib/features/products/products.dart
export 'domain/entities/product.dart';
export 'presentation/screens/products_screen.dart';
// Solo exporta APIs pΓΊblicas

❌ DON'T:

// No expongas implementaciones internas
export 'data/datasources/products_remote_datasource.dart';  // ❌
export 'data/models/product_model.dart';  // ❌

3. Dependencias entre Features

βœ… DO:

// Usa core para comunicaciΓ³n entre features
import 'package:app/core/services/event_bus.dart';

// O pasa datos a travΓ©s de navigation
context.push('/cart', extra: product);

❌ DON'T:

// No importes directamente desde otras features
import '../../products/domain/entities/product.dart';  // ❌

4. Testing por Feature

βœ… DO:

features/
  authentication/
    test/
      unit/
      widget/
      integration/

πŸ“š Recursos Adicionales

πŸ”— Skills Relacionados


VersiΓ³n: 1.0.0
Última actualización: Diciembre 2025