Skip to content
Back to skills

Feature First

ASecurity

| Atributo | Valor | |----------|-------| | **ID** | `flutter-feature-first` | | **Nivel** | 🟑 Intermedio | | **Versión** | 1.0.0 | | **Keywords** | `feature-first`, `feature-architecture`, `feature-driven` | | **Referencia** | [Feature-First Architecture Guide](https://codewithandrea.com/articles/flutter-project-structure/) |

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 8, 2026
toolsgoshelltestingapi

Works with

  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add chimeranext/flutter-boilerplate-monorepo-template --skill feature-first --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Feature First?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Feature First
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/chimeranext-feature-first/badge)](https://www.skillsdirectory.com/skills/chimeranext-feature-first)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# 🎨 Skill: Feature-First Architecture

## πŸ“‹ Metadata

| Atributo | Valor |
|----------|-------|
| **ID** | `flutter-feature-first` |
| **Nivel** | 🟑 Intermedio |
| **VersiΓ³n** | 1.0.0 |
| **Keywords** | `feature-first`, `feature-architecture`, `feature-driven` |
| **Referencia** | [Feature-First Architecture Guide](https://codewithandrea.com/articles/flutter-project-structure/) |

## πŸ”‘ 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

```yaml
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

```dart
// 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

```dart
// 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();
}
```

```dart
// 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

```dart
// 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];
}
```

```dart
// 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();
}
```

```dart
// 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

```dart
// 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,
    );
  }
}
```

```dart
// 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');
    }
  }
}
```

```dart
// 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

```dart
// 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)),
    );
  }
}
```

```dart
// 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;
}
```

```dart
// 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

```dart
// 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

```dart
// 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:**
```dart
// lib/features/products/products.dart
export 'domain/entities/product.dart';
export 'presentation/screens/products_screen.dart';
// Solo exporta APIs pΓΊblicas
```

❌ **DON'T:**
```dart
// No expongas implementaciones internas
export 'data/datasources/products_remote_datasource.dart';  // ❌
export 'data/models/product_model.dart';  // ❌
```

### 3. Dependencias entre Features

βœ… **DO:**
```dart
// 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:**
```dart
// 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

- [Flutter Project Structure by Andrea](https://codewithandrea.com/articles/flutter-project-structure/)
- [Feature-First Architecture](https://www.youtube.com/watch?v=z8NO_DpcfBM)

## πŸ”— Skills Relacionados

- [Clean Architecture](../clean-architecture/SKILL.md) - Arquitectura de cada feature
- [Modular Architecture](../modular-architecture/SKILL.md) - Alternativa modular
- [Testing Strategy](../testing/SKILL.md) - Testing de features

---

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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in tools β†’

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.