¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Luis Barroso 22/02/2023 Cargando comentarios…
Toda arquitectura Clean está compuesta por una serie de capas, las cuales separan ciertas partes de nuestra aplicación siguiendo los principios SOLID, para que su desarrollo, escalabilidad y mantenimiento sea fácil.
En este artículo veremos cómo la librería BLoC puede ayudarnos a gestionar el estado de nuestra aplicación, basándose en el manejo de eventos, siguiendo el patrón observer.
BLoC (Business Logic Component) es un componente que sirve de intermediario entre la vista y nuestro modelo de datos. Es similar al concepto de ViewModel (de la arquitectura MVVM), ya que nos facilita gestionar de una forma ordenada los cambios de estado que se producen en el modelo de datos de nuestra aplicación para que, de forma reactiva, la interfaz refleje los cambios necesarios. Se basa en el patrón observer, siguiendo la mecánica de escuchar eventos, y transformarlos en nuevos estados.
Si queremos actualizar la interfaz de nuestra aplicación, los widgets nos proporcionan una función setState, que tras ejecutar su contenido, genera la reconstrucción de los mismos, creando de nuevo una configuración que refleje dichos cambios. Por tanto, las variables que tenga nuestro widget, conforman el estado actual.
Por ejemplo, si tenemos un texto que muestra un contador y un botón, el cual incrementa dicho contador, el estado refleja el valor actual de ese número entero. En caso de incrementarlo invocamos a la función setState, que sumará 1 al valor actual, y provocará la actualización de nuestra interfaz:
Text('Pulsaciones: $_counter'),
ElevatedButton(
onPressed: () => setState(() {
_counter++;
}),
child: const Text("+1"),
)
Con ello, obtendremos el siguiente resultado:
Más adelante veremos cómo BLoC nos puede ayudar a gestionar este estado, para que nuestro código sea más sencillo de manejar.
En una arquitectura Clean, no pueden faltar las capas de Presentation, Domain, Data y Entity.
Al utilizar BLoC como gestor de estados, lo incluimos en la capa de presentación, siendo este el puente entre nuestra lógica de negocio y la interfaz de usuario.
BLoC quedaría situado de la siguiente manera:
Cada capa tiene su propia responsabilidad:
Como comentamos anteriormente, BLoC funciona siguiendo el patrón observer, en el cual, cuando el componente recibe un evento, lo procesa y genera un estado:
Volviendo al ejemplo anterior del contador, vamos a gestionar el estado, en el cual tenemos el valor del número entero, y tras un evento de incrementar, sumaremos una unidad y esto generará un nuevo estado con el valor + 1:
En el código, lo primero que debemos haces es crear una clase CounterState, la cual tendrá una variable con el contador:
class CounterState {
int counter;
CounterState({required this.counter});
}
También crearemos una clase evento, llamada IncrementEvent, para emitir un evento con la necesidad de incrementar el contador:
class IncrementEvent {}
Y, por último, crearemos la clase CounterBloc, la cual tiene como genéticos las clases CounterState e IncrementEvent. De esta clase, tenemos que tener en cuenta ciertos aspectos:
class CounterBloc extends Bloc<IncrementEvent, CounterState> {
CounterState _counterState = CounterState(counter: 0);
CounterBloc() : super(CounterState(counter: 0)) {
on<IncrementEvent>((event, emmit) {
_counterState = CounterState(counter: _counterState.counter + 1);
emit(_counterState);
});
}
}
Por último, tendremos que colocar el BLoC en la interfaz. Como en Flutter todo es un widget, añadiremos un BlocProvider a nuestro árbol de widgets. Este se encargará de crear nuestro BLoC, para que podamos acceder a él desde los widgets hijos.
Aunque posteriormente veremos que existen varias formas de consumir nuestro BLoC , en este caso utilizaremos un BlocBuilder, que se encargará de reconstruir los widgets hijos en caso de recibir un nuevo estado. Dentro de él, declararemos la función builder, que recibirá el contexto y el estado, del cual obtendremos los datos necesarios para mostrarlos en pantalla:
Center(
child: BlocProvider(
create: (context) => CounterBloc(),
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, counterState) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Pulsaciones: ${counterState.counter}'),
ElevatedButton(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.redAccent)),
onPressed: () {
BlocProvider.of<CounterBloc>(context).add(IncrementEvent());
},
child: const Text("+1"),
),
],
);
},
),
),
)
Como hemos visto en el ejemplo anterior, hay varias formas de inicializar un BLoC. De ello se encargan las clases provider que nos facilita la librería:
Widget blockProviderExample() {
return BlocProvider(
create: (BuildContext context) => CounterBloc(),
child: Text("This is a BLoC Provider"),
);
}
Widget multiBlockProviderExample() {
return MultiBlocProvider(
providers: [
BlocProvider(create: (BuildContext context) => CounterBloc()),
BlocProvider(create: (BuildContext context) => CounterLessBloc()),
],
child: Text("This is a Multi BLoC Provider"),
);
}
class Bloc1 extends Bloc<Event1, State1> {
Repository1 repository1;
Bloc1({required this.repository1}) : super(State1());
}
class Event1 {}
class State1 {}
Widget repositoryBlockProviderExample() {
return RepositoryProvider(
create: (context) => Repository1(),
child: BlocProvider(
create: (BuildContext context) => Bloc1(repository1: context.read<Repository1>()),
child: Text("This is a repository BLoC Provider"),
),
);
}
class Repository1 {}
class Repository2 {}
class Bloc2 extends Bloc<Event1, State1> {
Repository1 repository1;
Repository2 repository2;
Bloc2({required this.repository1, required this.repository2}) : super(State1());
}
class Event1 {}
class State1 {}
Widget multuRepositoryBlockProviderExample() {
return MultiRepositoryProvider(
providers: [
RepositoryProvider(create: (context) => Repository1()),
RepositoryProvider(create: (context) => Repository2()),
],
child: BlocProvider(
create: (BuildContext context) => Bloc2(
repository1: context.read<Repository1>(),
repository2: context.read<Repository2>(),
),
child: Text("This is a multirepository BLoC Provider"),
),
);
}
Una vez tenemos inicializados nuestros BLoCs, solo tendremos que utilizarlos según las necesidades que tengamos:
Widget blocListenerExample(){
return BlocListener<Bloc1,State1>(listener: (context, state){
if(state.runtimeType == State1){
//realizar una acción que no esté
//ligada a nuestro árbol de widgets
//por ejemplo: mostrar un Toast
//o un snackbar
}
});
}
Widget blockBuilderExample(){
return BlocBuilder<Bloc1,State1>(builder: (context, state){
if(state.runtimeType == State1){
return const CircularProgressIndicator();
}else{
return const Text("Mostrar datos");
}
});
}
Widget blockConsumerExample() {
return BlocConsumer<Bloc1, State1>(listener: (context, state) {
if (state.hashCode == 0) {
context.read<Bloc1>().add(Event1());
}
}, builder: (context, state) {
if (state.runtimeType == State1) {
return const CircularProgressIndicator();
} else {
return const Text("Mostrar datos");
}
});
}
En este ejemplo, vamos a simular cómo obtenemos una mano de 5 cartas de un mazo. Tomamos como base un servicio REST, que nos genera un mazo de cartas, del cual vamos a poder obtener una mano de 5 cartas.
Por tanto, tenemos un BLoC para recibir ambos estados. A esta clase la llamaremos CardsBloc.
El resultado, sería el siguiente:
En el siguiente enlace, se puede consultar el código completo, que cuenta con:
Con esto hemos visto los usos más comunes del paquete BLoC, como intermediario entre la capa de interfaz y el modelo de negocio, así como varios ejemplos dependiendo de la casuística que tengamos. BLoC es una pieza que nos es muy familiar si venimos de usar ViewModels o Presenters, y que nos ayuda a crear una máquina de estado reactiva a eventos. Hay otras opciones similares, como Riverpod, Provider o GetIt, pero BLoC es más sencilla, aun requiriendo un poco de boilerplate para montarlo.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.