# Modularity (Modularización)
## ¿Qué es la Modularización?
La modularización es el proceso de dividir una aplicación en módulos independientes, cada uno con una
responsabilidad específica. En Flutter, esto significa organizar el código en paquetes reutilizables y
desacoplados.
## Beneficios de la Modularización
### 1. **Reutilización de Código**
### 2. **Desarrollo Paralelo**
### 3. **Compilación más Rápida**
### 4. **Fácil Mantenimiento**
### 5. **Testing Simplificado**
## Visualización de Dependencias
```mermaid
graph TD
subgraph "App"
Main[Main App]
end
subgraph "Features"
Auth[Authentication Feature]
Products[Products Feature]
Cart[Cart Feature]
end
subgraph "Core"
Network[Network Module]
Theme[Theme Module]
Utils[Utils Module]
end
Main --> Auth
Main --> Products
Main --> Cart
Auth --> Network
Auth --> Utils
Products --> Network
Products --> Theme
Cart --> Products
style Main fill:#f9f,stroke:#333,stroke-width:2px
style Auth fill:#bbf,stroke:#333,stroke-width:2px
style Products fill:#bbf,stroke:#333,stroke-width:2px
style Cart fill:#bbf,stroke:#333,stroke-width:2px
style Network fill:#dfd,stroke:#333,stroke-width:2px
style Theme fill:#dfd,stroke:#333,stroke-width:2px
style Utils fill:#dfd,stroke:#333,stroke-width:2px
```
## Tipos de Modularización en Flutter
### Modularización por Features
```
lib/
├── features/
│ ├── authentication/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ ├── products/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ └── cart/
│ ├── data/
│ ├── domain/
│ └── presentation/
└── core/
├── network/
├── theme/
└── utils/
```
### Modularización por Packages
```
my_app/
├── packages/
│ ├── core/
│ │ └── lib/
│ │ ├── error/
│ │ ├── network/
│ │ └── utils/
│ ├── authentication/
│ │ ├── lib/
│ │ └── pubspec.yaml
│ └── products/
│ ├── lib/
│ └── pubspec.yaml
└── pubspec.yaml
```
## Implementación Práctica
### Paso 1: Crear un Módulo Independiente
```yaml
# packages/authentication/pubspec.yaml
name: authentication
description: Authentication module
version: 1.0.0
environment:
sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter # Dependencias específicas del módulo flutter_bloc:
^8.1.0 dartz: ^0.10.1 equatable: ^2.0.0 ``` ### Paso 2: Definir la Interface Pública del Módulo ```dart //
packages/authentication/lib/authentication.dart library authentication; // Exportar solo lo necesario
export 'src/domain/entities/user.dart' ; export 'src/domain/usecases/login.dart' ;
export 'src/domain/usecases/logout.dart' ; export 'src/presentation/bloc/auth_bloc.dart' ;
export 'src/presentation/screens/login_screen.dart' ; // NO exportar detalles de implementación // ❌ No hacer:
export 'src/data/datasources/...' ; ``` ### Paso 3: Implementar el Módulo ```dart //
packages/authentication/lib/src/domain/entities/user.dart class User { final String id; final String name;
final String email; User({required this.id, required this.name, required this.email}); } //
packages/authentication/lib/src/domain/repositories/auth_repository.dart abstract class AuthRepository {
Future> login(String email, String password);
Future> logout();
Future> getCurrentUser();
}
// packages/authentication/lib/src/domain/usecases/login.dart
class Login {
final AuthRepository repository;
Login(this.repository);
Future> call(String email, String password) {
return repository.login(email, password);
}
}
```
### Paso 4: Usar el Módulo en la App Principal
```yaml
# pubspec.yaml de la app principal
dependencies:
flutter:
sdk: flutter
authentication:
path: packages/authentication
products:
path: packages/products
```
```dart
// main.dart
import 'package:authentication/authentication.dart';
import 'package:products/products.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => sl()),
BlocProvider(create: (_) => sl()),
],
child: MaterialApp(
home: AuthWrapper(),
),
);
}
}
```
## Patrón Feature-First Structure
```dart
// lib/features/products/products.dart
library products;
export 'domain/entities/product.dart';
export 'presentation/bloc/products_bloc.dart';
export 'presentation/pages/products_page.dart';
export 'di/products_injection.dart';
// Inyección de dependencias del módulo
class ProductsModule {
static void initialize() {
// Register
dependencies
sl.registerLazySingleton(
() => ProductRepositoryImpl(
remoteDataSource: sl(),
localDataSource: sl(),
),
);
sl.registerLazySingleton(() => GetProducts(sl()));
sl.registerFactory(() => ProductsBloc(getProducts: sl()));
}
}
```
## Comunicación Entre Módulos
### ❌ Mal: Acoplamiento Directo
```dart
// products_bloc.dart
class ProductsBloc extends Bloc {
// ❌ Dependencia directa de otro módulo
final AuthBloc authBloc;
ProductsBloc(this.authBloc) {
// Acoplamiento fuerte
if (authBloc.state is Authenticated) {
add(LoadProducts());
}
}
}
```
### ✅ Bien: Event Bus o Streams
```dart
// core/events/app_events.dart
abstract class AppEvent {}
class UserLoggedIn extends AppEvent {
final String userId;
UserLoggedIn(this.userId);
}
class UserLoggedOut extends AppEvent {}
// core/events/event_bus.dart
class EventBus {
final _controller = StreamController.broadcast();
Stream get events => _controller.stream;
void fire(AppEvent event) {
_controller.add(event);
}
}
// authentication module
class AuthBloc extends Bloc {
final EventBus eventBus;
Future _onLoginSuccess(User user) async {
emit(Authenticated(user));
eventBus.fire(UserLoggedIn(user.id)); // ✅ Comunicación desacoplada
}
}
// products module
class ProductsBloc extends Bloc {
final EventBus eventBus;
StreamSubscription? _eventSubscription;
ProductsBloc(this.eventBus) {
_eventSubscription = eventBus.events.listen((event) {
if (event is UserLoggedIn) {
add(LoadProducts());
} else if (event is UserLoggedOut) {
add(ClearProducts());
}
});
}
@override
Future close() {
_eventSubscription?.cancel();
return super.close();
}
}
```
## Módulos Core Comunes
### 1. Core/Network Module
```dart
// core/network/network_module.dart
class NetworkModule {
static Dio createDio() {
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
));
dio.interceptors.addAll([
LogInterceptor(),
AuthInterceptor(),
RetryInterceptor(),
]);
return dio;
}
}
```
### 2. Core/Theme Module
```dart
// core/theme/app_theme.dart
class AppTheme {
static ThemeData get lightTheme => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
);
static ThemeData get darkTheme => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
);
}
```
### 3. Core/Utils Module
```dart
// core/utils/validators.dart
class Validators {
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Invalid email format';
}
return null;
}
}
```
## Gestión de Dependencias Entre Módulos
```yaml
# packages/products/pubspec.yaml
dependencies:
# ✅ Solo dependencias necesarias
core:
path: ../core
# ❌ Evitar dependencias circulares
# authentication:
# path: ../authentication
```
## Testing de Módulos
```dart
// packages/authentication/test/domain/usecases/login_test.dart
void main() {
late Login usecase;
late MockAuthRepository mockRepository;
setUp(() {
mockRepository = MockAuthRepository();
usecase = Login(mockRepository);
});
test('should return User when credentials are correct', () async {
// Arrange
final tUser = User(id: '1', name: 'Test', email: '[email protected]');
when(mockRepository.login(any, any))
.thenAnswer((_) async => Right(tUser));
// Act
final result = await usecase('[email protected]', 'password');
// Assert
expect(result, Right(tUser));
});
}
```
## Mejores Prácticas
### ✅ Hacer
1. **Mantener módulos pequeños y enfocados**
2. **Definir interfaces claras**
3. **Documentar APIs públicas**
4. **Versionar módulos independientemente**
5. **Minimizar dependencias entre módulos**
### ❌ Evitar
1. **Dependencias circulares**
2. **Exposer detalles de implementación**
3. **Módulos demasiado grandes**
4. **Acoplamiento fuerte entre módulos**
## Herramientas Útiles
### Melos - Gestión de Monorepos
```yaml
# melos.yaml
name: my_app
packages:
- packages/**
scripts:
analyze:
run: flutter analyze
exec:
concurrency: 1
test:
run: flutter test
exec:
concurrency: 1
```
```bash
# Instalar melos
dart pub global activate melos
# Bootstrap (instalar dependencias de todos los paquetes)
melos bootstrap
# Ejecutar tests en todos los paquetes
melos test
# Analizar todos los paquetes
melos analyze
```
## Conclusión
La modularización efectiva en Flutter:
- ✅ **Mejora la escalabilidad**
- ✅ **Facilita el testing**
- ✅ **Permite desarrollo paralelo**
- ✅ **Reduce tiempos de compilación**
- ✅ **Aumenta la reutilización de código**
> "Divide y vencerás: Los módulos pequeños y bien definidos son más fáciles de
entender, testear y mantener."
Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.
Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.