Cómo usar el manejo de estado BLoC en Flutter

El manejo de estado es la parte de una app en Flutter que decide en silencio si el código sigue siendo agradable de trabajar o se convierte en una maraña. BLoC, abreviatura de Business Logic Component, es uno de los patrones más usados para mantener eso bajo control. La idea es simple: saca la lógica de negocio de tus widgets y deja que ambos lados se comuniquen por streams. Esto es lo que significa en la práctica y cómo se conecta.
Qué es BLoC en realidad
BLoC es un patrón de arquitectura que separa la capa de presentación (tu interfaz) de la capa de lógica de negocio. Creas una clase dedicada, el BLoC, que es dueña del estado de tu app y lo expone a la interfaz mediante streams. El widget envía eventos hacia adentro; el BLoC emite nuevos estados hacia afuera. Como los dos nunca se meten directamente uno en el otro, tu interfaz se mantiene delgada y tu lógica se mantiene fácil de probar.

Por qué los equipos lo eligen
Hay algunas ganancias concretas una vez que el patrón está en su lugar:
- Separación de responsabilidades. Con la capa de presentación separada de la lógica de negocio, el código se mantiene ordenado y mucho más fácil de mantener conforme la app crece.
- Reutilización. Como la lógica vive fuera de los widgets, puedes reutilizar el mismo BLoC en varias pantallas y componentes en lugar de duplicarlo.
- Facilidad de prueba. Puedes probar la lógica de negocio por su cuenta, sin levantar la interfaz, lo que hace que las pruebas automatizadas sean más rápidas de escribir y más confiables.
BLoC en acción
Un ejemplo completo es la forma más clara de verlo. Vamos a construir una pequeña app contador, el “hola mundo” del manejo de estado en Flutter, en tres pasos.
Paso 1: define los eventos y el BLoC
Empieza por los eventos. Modelarlos como una clase sellada permite que el compilador te avise cuando falta un manejador, que es la razón principal por la que hoy se prefiere sobre un enum simple en Dart.
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}Después, el BLoC. Extiende Bloc del paquete flutter_bloc, pasa su estado inicial a super y registra un manejador por evento con on<Event>. Cada manejador recibe el evento y una función emit, y llamar a emit envía un nuevo estado a la interfaz. Para el contador, el estado es simplemente un entero.
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}Si has leído tutoriales viejos de BLoC quizá reconozcasmapEventToState, un método único que devolvía un stream de estados. Se eliminó en bloc 8 y no compila con las versiones actuales. La formaon<Event>+emitde arriba lo reemplaza.
Paso 2: construye tu interfaz
Sigue la pantalla. Fíjate en que el widget no guarda ningún valor de contador propio: lee la cuenta actual del BLoC y despacha un evento CounterIncrementPressed o CounterDecrementPressed cuando se tocan los botones.
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Center(
child: Text('$count', style: const TextStyle(fontSize: 24)),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () =>
context.read<CounterBloc>().add(CounterIncrementPressed()),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () =>
context.read<CounterBloc>().add(CounterDecrementPressed()),
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
);
}
}Paso 3: conecta la interfaz con el BLoC
Por último, entrega el BLoC a la interfaz. El widget BlocBuilder de flutter_bloc se reconstruye cada vez que cambia el estado de CounterBloc, y context.read() les da a los botones una referencia para despachar eventos. Envolver la página en un BlocProvider es lo que deja ese BLoC disponible para todo lo que está debajo.
BlocProvider(
create: (_) => CounterBloc(),
child: const CounterPage(),
)El patrón siempre vuelve a la misma forma: la interfaz agrega eventos, un manejador registrado emite nuevos estados y los widgets se reconstruyen. Una vez que eso hace clic, pasar de un contador a una app real es la misma idea repetida.
Cuándo conviene más un Cubit
BLoC no es la única opción dentro del mismo paquete. Un Cubit elimina por completo la capa de eventos y expone métodos simples que llaman a emit, lo que implica menos ceremonia para estados que cambian de forma evidente. Recurre a un BLoC completo cuando quieras un registro auditable de qué ocurrió y no solo de qué cambió, algo que importa para depurar, para analítica y para reproducir el recorrido de un usuario.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}BLoC no es la única forma de manejar el estado en Flutter, pero se gana su lugar en equipos que valoran las fronteras limpias y una buena cobertura de pruebas. Si lo estás evaluando para un proyecto, o quieres que lo haga un equipo que ya ha lanzado muchas apps en Flutter, cuéntanos qué estás construyendo, o échale un vistazo a las apps que hemos lanzado.
¿Estás pensando en construir esto?
Appluex diseña y lanza apps móviles y web en producción, incluidas funciones de inteligencia artificial. Hablemos.