Back to skills
SKILL.md
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
Works with
Security analysis
100/100npx -y skills add chimeranext/flutter-boilerplate-monorepo-template --skill feature-first --agent claude-codeAre you the author of Feature First?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/chimeranext-feature-first)# π¨ 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
Related skills
View all in tools βComments
Loading commentsβ¦
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.