Guía completa de BLoC en Flutter
================================
1. Introducción a BLoC
----------------------
BLoC (Business Logic Component) es un patrón de arquitectura para Flutter que separa la lógica de negocio de la
interfaz de usuario, manteniendo un flujo reactivo basado en eventos y estados.
Ciclo básico:
UI → Evento → BLoC → Estado → UI
Ventajas:
• Facilita los tests unitarios.
• Permite reusar lógica.
• Mantiene la UI declarativa y reactiva.
2. Estructura básica de archivos
--------------------------------
/bloc
counter_bloc.dart
counter_event.dart
counter_state.dart
3. Ejemplo Counter BLoC
-----------------------
counter_event.dart
------------------
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
counter_state.dart
------------------
class CounterState {
final int counter;
CounterState([Link]);
}
counter_bloc.dart
-----------------
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0)) {
on<IncrementEvent>((event, emit) {
emit(CounterState([Link] + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterState([Link] - 1));
});
}
}
4. Métodos principales
----------------------
• on((event, emit) {...}) – Asocia un tipo de evento a un handler y emite estados.
• state – Estado actual (inmutable).
• emit(newState) – Notifica a la UI un nuevo estado.
5. Conexión con la UI
---------------------
• BlocProvider – Inyecta el BLoC en el árbol.
• BlocBuilder – Reconstruye la UI cuando cambia el estado.
• BlocListener – Ejecuta efectos secundarios no■visuales.
6. BLoC Avanzado
----------------
transformEvents / transformTransitions
--------------------------------------
Ejemplo de debounce:
@override
Stream<Transition<CounterEvent, CounterState>> transformEvents(
Stream<CounterEvent> events,
TransitionFunction<CounterEvent, CounterState> transitionFn,
) {
return [Link](
[Link](const Duration(milliseconds: 300)),
transitionFn,
);
}
BlocObserver
------------
Permite loggear todos los eventos y transiciones:
class MyBlocObserver extends BlocObserver {
@override
void onEvent(Bloc bloc, Object? event) { print(event); }
@override
void onTransition(Bloc bloc, Transition t) { print(t); }
}
7. Testing con bloc_test
------------------------
blocTest<CounterBloc, CounterState>(
'emits [1] when IncrementEvent is added',
build: () => CounterBloc(),
act: (bloc) => [Link](IncrementEvent()),
expect: () => [CounterState(1)],
);
8. Mejores prácticas
--------------------
• Estados y eventos inmutables (freezed / equatable).
• Solo incluir datos necesarios en el estado.
• Testear BLoC y repositorios.
• Usar BlocSelector para optimizar renders.
• Modularizar la app.
9. Proyecto completo: Posts App
-------------------------------
Pasos:
1. Crear proyecto `flutter create bloc_posts_app`.
2. Añadir dependencias: flutter_bloc, http, equatable, bloc_test, mocktail.
3. Crear modelos, repositorio, BLoC, UI y tests.
Estructura:
/lib
/bloc
post_bloc.dart
post_event.dart
post_state.dart
/models
post_model.dart
/repository
post_repository.dart
[Link]
post_page.dart
Código relevante
----------------
post_model.dart
class Post {
final int id; final String title; final String body;
Post({required [Link], required [Link], required [Link]});
factory [Link](Map<String,dynamic> json)=>
Post(id:json['id'], title:json['title'], body:json['body']);
}
post_repository.dart
class PostRepository {
Future<List<Post>> fetchPosts() async {
final res = await [Link]([Link](
'[Link]
if ([Link]==200) {
final list=[Link]([Link]) as List;
return [Link]((e)=>[Link](e)).toList();
} else { throw Exception('Failed'); }
}
}
post_event.dart
abstract class PostEvent {}
class FetchPosts extends PostEvent {}
post_state.dart
abstract class PostState extends Equatable { ... }
class PostInitial extends PostState {}
class PostLoading extends PostState {}
class PostLoaded extends PostState { final List<Post> posts; ... }
class PostError extends PostState { final String message; ... }
post_bloc.dart
class PostBloc extends Bloc<PostEvent, PostState> {
final PostRepository repo;
PostBloc([Link]):super(PostInitial()) {
on<FetchPosts>((e,emit) async {
emit(PostLoading());
try{ emit(PostLoaded(await [Link]())); }
catch(_){ emit(PostError('Error fetching posts')); }
});
}
}
UI principal
------------
BlocProvider(
create: (_) => PostBloc(PostRepository())..add(FetchPosts()),
child: PostPage(),
)
post_page.dart usa BlocBuilder para mostrar estados.
Tests
-----
blocTest<PostBloc, PostState>(
'success path',
build: ()=>PostBloc(mockRepo),
act: (bloc)=>[Link](FetchPosts()),
expect: ()=>[PostLoading(), PostLoaded(posts)],
);
10. Próximos pasos
------------------
• Agregar paginación infinita con `ScrollController` y eventos de carga.
• Implementar búsqueda local.
• Persistir datos con `hydrated_bloc`.
• Manejo de autenticación con múltiples BLoCs.
Fin de la guía.