top of page
Alejandro Carrazana

Tutorial de Flutter Bloc para el manejo de estados

En Flutter, existen múltiples paquetes para manejar el estado de una aplicación. Estos paquetes proporcionan distintas formas de organizar y manejar el estado de una aplicación, pero todos tienen el mismo objetivo: facilitar la creación de aplicaciones escalables y fáciles de mantener.


En este artículo daremos a conocer todos los detalles sobre el paquete flutter_bloc en la gestión de estados.


Introducción a Flutter BLoC

Flutter BLoC es una arquitectura de aplicaciones móviles basada en el patrón BLoC (Business Logic Component). La arquitectura BLoC se enfoca en la separación de la lógica de negocio de la interfaz de usuario, permitiendo una mayor escalabilidad y facilidad de mantenimiento del código.


Flutter BLoC se basa en el uso de Streams y StreamBuilders para conectar la lógica de negocio con la vista. Esto permite a los desarrolladores crear "bloques" de lógica de negocio que manejan estados y eventos, pudiéndose conectar a la vista a través de StreamBuilders.


El uso de Flutter BLoC permite una mayor organización y escalabilidad del código, ya que se separa la lógica de negocio de la interfaz de usuario. Esto permite una mayor independencia entre las distintas partes de la aplicación, lo que a su vez ayuda en la facilidad de mantenimiento y mejora del rendimiento.


¿Qué es el paquete flutter_bloc?


El paquete flutter_bloc es un paquete para Flutter que permite la implementación de la arquitectura BLoC (Business Logic Component) de manera más simple. Esta arquitectura se enfoca en la separación de la lógica de negocio de la interfaz de usuario, logrando una mayor escalabilidad y facilidad de mantenimiento del código.


Con flutter_bloc, se pueden crear fácilmente "bloques" de lógica de negocio que manejan estados y eventos. Además, flutter_bloc proporciona herramientas para manejar errores y excepciones, así como para integrar con servicios externos como Firebase.


Flutter Bloc fue diseñado con tres valores fundamentales en mente:

  • Simple: fácil de entender y ser utilizado por desarrolladores con diferentes niveles de habilidad.

  • Potente: ayudar a crear aplicaciones asombrosas y complejas al utilizar componentes más pequeños.

  • Comprobable: probar fácilmente todos los aspectos de una aplicación para que podamos iterar con confianza.

En general, flutter_bloc intenta hacer que los cambios de estado sean predecibles regulando cuándo puede ocurrir un cambio de estado y aplicando una sola forma de cambiar de estado en toda la aplicación.


Para más información le recomendamos visitar la documentación oficial en Github.


Implementación de flutter_bloc


El siguiente ejemplo es una simple aplicación contador que nos permitirá exponer todos los aspectos que conlleva la integración del paquete flutter_bloc en el manejo de los estados y la lógica de la aplicación.


En este contador, luego de presionar el botón “+”, se incrementa en 1 el número que se muestra al usuario, como particularidad le agregaremos una demora de 1 segundo antes de realizar el cálculo. En aplicaciones más complejas, esta demora puede estar relacionada con solicitudes HTTP o un procesamiento más avanzado de la información.

Primero empezaremos agregando el paquete flutter_bloc: ^8.1.1 al fichero pubspec.yaml. Dividiremos el proyecto de la siguiente forma, a la página home_page.dart le asociaremos la lógica bloc relacionada al contador:

Como se observa, existen tres elementos fundamentales a la hora de implementar este contador con flutter_bloc:

  • el fichero counter_state, donde se manejan los diferentes estados de la lógica (InitCount, Counting, Counted y CountError).

part of 'counter_bloc.dart';
abstract class CounterState {
 final int num = 0;
 List<Object?> get props => [];
}
class InitCount extends CounterState {
 final int num;
 InitCount({required this.num});
 @override
 List<Object?> get props => [num];
}

class Counting extends CounterState {
 @override
 List<Object?> get props => [];
}
class Counted extends CounterState {
 final int num;
 Counted({required this.num});
 @override
 List<Object?> get props => [num];
}

class CountError extends CounterState {
 final String error;
 CountError({required this.error});
 @override
 List<Object?> get props => [error];
}
  • el fichero counter_event, donde se gestionan los eventos que tienen lugar (Increment).

part of 'counter_bloc.dart';
abstract class CountEvent {}
class Increment extends CountEvent {}
  • el fichero counter_bloc, donde se escuchan los eventos y emiten los estados en base al procesamiento que se realice de los datos.

import 'package:flutter_bloc/flutter_bloc.dart';
part 'counter_state.dart';
part 'counter_event.dart';

class CounterBloc extends Bloc<CountEvent, CounterState> {
 static int num = 0;

 CounterBloc() : super(InitCount(num: num)) {
   on<Increment>((event, emit) async {
     try {
       emit(Counting());
       await Future.delayed(const Duration(seconds: 1));
       emit(Counted(num: ++num));
     } catch (e) {
       emit(CountError(error: e.toString()));
       emit(InitCount(num: num));
     }
   });
 }
}

El fichero main quedaría de la siguiente forma:

void main() {
 runApp(const BlocDemoApp());
}
class BlocDemoApp extends StatelessWidget {
 const BlocDemoApp({super.key});
 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Bloc Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: BlocProvider(
       create: (context) => CounterBloc(),
       child: const HomePage(title: 'Bloc Demo'),
     ),
   );
 }
}

Apreciamos como se ha implementado BlocProvider sobre la clase HomePage, pasando en el parámetro create un objeto de la clase CounterBloc.


Un BlocProvider se utiliza para crear un objeto BLoC y que este sea accesible para los widgets hijos en la jerarquía de widgets. Los widgets hijos pueden usar el objeto BLoC, a través de un contexto (context), para actualizar su estado y manejar eventos.


Tal es el caso de la clase HomePage, dentro del fichero home_page.dart, que emplea el objeto CounterBloc mediante context.

class HomePage extends StatelessWidget {
 final String title;
 const HomePage({super.key, required this.title});

 @override
 Widget build(BuildContext context) {
   return BlocBuilder<CounterBloc, CounterState>(
     builder: (context, state) {
       return Scaffold(
         appBar: AppBar(
           title: Text(title),
         ),
         body: Center(
           child: Column(
             mainAxisAlignment: MainAxisAlignment.center,
             children: <Widget>[
               const Text(
                 'You have pushed the button this many times:',
               ),
               Text(
                 (state is InitCount || state is Counted)
                     ? '${state.num}'
                     : (state is Counting ? "Counting..." : "Error"),
                 style: Theme.of(context).textTheme.headline4,
               ),
             ],
           ),
         ),
         floatingActionButton: Visibility(
           visible: state is! Counting,
           child: FloatingActionButton(
             onPressed: () => context.read<CounterBloc>().add(Increment()),
             tooltip: 'Increment',
             child: const Icon(Icons.add),
           ),
         ), // This trailing comma makes auto-formatting nicer for build methods.
       );
     },
   );
 }
}

El botón floatingActionButton, una vez que sea presionado por el usuario, ejecutará el evento Increment mediante el contexto:

context.read<CounterBloc>().add(Increment())

Este será escuchado por CounterBloc como se expone. Seguidamente se emite el estado Counting, se espera un segundo y luego incrementa el número. En caso de error lanzará el estado CountError.

on<Increment>((event, emit) async {
     try {
       emit(Counting());
       await Future.delayed(const Duration(seconds: 1));
       emit(Counted(num: ++num));
     } catch (e) {
       emit(CountError(error: e.toString()));
       emit(InitCount(num: num));
     }
   });

En el lado de la vista del usuario, es decir, la clase HomePage, se escucharán los estados con la ayuda del widget BlocBuilder.

Text(
 (state is InitCount || state is Counted)
     ? '${state.num}'
     : (state is Counting ? "Counting..." : "Error"),
 style: Theme.of(context).textTheme.headline4,
),

BlocBuilder es un widget útil para escuchar los cambios en el estado de un BLoC y actualizar la vista en consecuencia. BlocBuilder tiene una sintaxis similar a StreamBuilder, pero tiene algunas ventajas adicionales, como la posibilidad de proporcionar un estado inicial y la posibilidad de manejar errores y excepciones, permitiendo una mayor organización y escalabilidad del código.


En resumen, el paquete flutter_bloc es una herramienta poderosa para desarrollar aplicaciones móviles con Flutter siguiendo la arquitectura BLoC, permitiendo una mayor organización y escalabilidad del código.


¡Síguenos para más contenidos sobre el desarrollo de aplicaciones Flutter!





bottom of page