Muy buenas, soy Luis y en esta ocasión les traigo este artículo.
Índice
Lograr un código más limpio en Flutter con Bloc
Si ha estado desarrollando aplicaciones Flutter, sabe que es crucial administrar el estado de su aplicación de la mejor manera posible.
Flutter nos proporciona muchas opciones de administración de estados, como Provider
, Bloc
, Redux
y MobX
.
Hoy veremos Bloc
.
Entiendo que puede ser un poco más difícil de entender, también lo fue para mí. Pero ahora creo que lo entiendo, así que estoy aquí para simplificarlo.
Sabemos que en Flutter es muy fácil mezclar la lógica de la interfaz de usuario con la lógica de su negocio, que no es realmente la mejor manera de desarrollar aplicaciones fáciles de mantener.
Bloc
viene al rescate dando una mejor estructura a su aplicación, separando la interfaz de usuario y la lógica empresarial de su aplicación. Pero antes de entrar en cómo implementarlo, primero comprendamos …
El artículo va a ser un poco largo, pero usted, como principiante, se irá con la confianza de cómo usar Bloc
al final.
¿Qué es Bloc de todos modos?
Bloc
significa componente y lógica empresarial. Es como una caja negra que envuelve toda la lógica de su negocio (y componentes relacionados) y asigna varios eventos de la aplicación a los estados de la aplicación.
Si esa definición fue demasiado técnica para usted, intentemos comprenderla mejor con algunos ejemplos.
Si está utilizando una aplicación meteorológica
Habrá varios eventos en su aplicación, como getWeather()
setLocation()
y resetWeather()
se activarán ante las acciones del usuario o alguna lógica interna de su aplicación.
Estas acciones actualizarán la interfaz de usuario de su aplicación con un nuevo estado en consecuencia después de que la operación se realice correctamente.
Aquí solo hay un evento, y ese es el incrementCounter
que genera el nuevo estado, que muestra el valor incrementado del contador.
Bloc
es la caja negra que le dirá qué operación debe realizarse en qué evento y luego qué estado debe generarse después de que se complete la operación.
¿Cómo define el bloque para su aplicación?
Un gran inconveniente de Bloc
es la cantidad de código repetitivo que tenemos que escribir, incluso solo para los cambios más pequeños de la interfaz de usuario.
Ese es el único inconveniente que he visto hasta ahora, pero ciertamente simplifica el código de su aplicación al separar toda la lógica de la interfaz de usuario de la lógica empresarial.
Por lo tanto, puede tomar todo el código estándar como una inversión necesaria para mantener su código fácil de mantener y comprender con Bloc
.
Por ejemplo, supongamos que está creando una aplicación meteorológica con Bloc
. Ha definido todos sus eventos y estados y su aplicación funciona perfectamente.
Ahora, si tiene que agregar otra característica dentro de la misma pantalla, puede simplemente seguir adelante y definir un nuevo evento y un nuevo estado dentro de su Bloc
con un poco de lógica sobre cómo mapear el nuevo evento a un nuevo estado.
¡Listo! No tiene que tocar / interrumpir otros eventos y estados construidos, y puede agregar su nuevo conjunto de funciones sin mucho problema.
Esto es similar a lo que le dice el principio abierto-cerrado
de los principios SOLID.
Qué estaremos construyendo
Crearemos una versión modificada de la aplicación de contador, que le ayudará a obtener una imagen más clara de Bloc
.
Habrá cuatro botones:
- Incrementar el valor del contador: actualiza instantáneamente el valor del contador.
- Disminuir el valor del contador: actualiza instantáneamente el valor del contador.
- Generar valor aleatorio: simularemos un retraso y luego actualizaremos el valor.
Si entendió los requisitos, puede ver que lo que hemos marcado en negrita arriba son eventos, y la explicación a la derecha de los dos puntos son los estados.
1. Agregue estas dependencias a su pubspec.yaml
A partir de marzo de 2020
, agregaré esta dependencia para Flutter
.
flutter_bloc: ^ 3.2.0
equatable: ^ 1.1.0
Bloc
es la biblioteca creada por el equipo de bloclibrary.dev
y proporciona los fundamentos básicos del patrón Bloc
. Pero es un poco más complejo, por lo tanto, lo flutter_bloc
creó el mismo equipo solo para facilitar las cosas a los desarrolladores de Flutter.
2. Crea tu clase 'Bloc'
Mi código de UI estará CounterScreen
dentro del archivo counter_screen.dart
.
(Llegaremos a la parte de la interfaz de usuario más adelante).
Crea un nuevo archivo Dart para tu clase Bloc
con el nombre counter_screen_bloc.dart
. Definiremos este archivo Bloc
de clase.
Dentro de la pantalla Bloc
, definiremos tres cosas:
- Evento: para mostrar todos los eventos posibles.
- Estado: para mostrar todos los estados posibles.
- Bloc: cómo asignar esos eventos a los estados.
Estas son las declaraciones de importación necesarias para los siguientes pasos.
importar 'paquete: bloc / bloc.dart'; importar 'paquete: equatable / equatable.dart';
3. Definir eventos
Dentro del archivo Bloc
de clase en blanco , comience definiendo un evento.
Lo he nombrado CounterScreenEvent
.
Equatable
hace que nuestras clases sean equiparables / comparables, lo cual es necesario para la lógica de mapeo dentro de la clase Bloc
.
Equatable
le pide que anule lo get props
que hemos hecho aquí.
4. Definir estados
Debajo de estos, definamos los estados a los que se asignará cada uno de ellos.
Bueno, el usuario verá los valores de contador incrementados / decrementados en la pantalla, o verá una pantalla de carga mientras intentamos generar un número aleatorio simulando una llamada de red.
El estado ShowCounterValue
tendrá un valor de contador asociado. Por lo tanto, definimos una propiedad en esa clase llamada counterValue
para ayudarlo a pasar el valor del contador a la interfaz de usuario de la aplicación desde Bloc
.
5. Finalmente, defina nuestro 'bloque'
Sí, finalmente podemos definir nuestro Bloc
.
/// Clase BLoC DashboardScreenBLoC extiende Bloc < CounterScreenEvent , CounterScreenState > { int counterValue = 0;@override CounterScreenState obtener initialState => ShowCounterValue ( counterValue ); @override Stream <CounterScreenState> mapEventToState (evento CounterScreenEvent) async * { // TODO: Map event to state } }
- En la primera línea, tenemos
DashboardScreenBloc
, que se extiendeBloc<Event,State>
como hemos definido anteriormente. - Definimos una propiedad para nuestro contravalor llamada
counterValue
, que se utilizará para mantener el estado de nuestro contador. - Ahora tenemos que definir
initialState
. Cuando elBloc
cargas, el estado inicial se establecerá enShowCounterValue(counterValue)
. - Finalmente, tenemos una función
mapEventToState
(que hace exactamente lo que su nombre indica). Si echas un vistazo a la función, verás que está marcada comoasync*
.
Solo para mantener a todos en la misma página, voy a explicar async
vs async*
.
Si su función devuelve un valor futuro después de realizar una determinada operación, marcamos esa función como async
. Estas funciones devuelven un valor solo una vez y su ejecución finaliza con la declaración de retorno.
La función marcada como async*
, por otro lado, devuelve una secuencia. Esta función produce diferentes valores y no solo los devuelve. Su ejecución aún continúa y pueden producir tantos valores como desee.
Es por eso que su salida se llama flujo.
Terminemos nuestro Bloc
mapeando eventos a estados.
A continuación se muestra el código completo counter_screen_bloc.dart
.
import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc_counter_example/counter_screen/CounterRepo.dart'; /// Events class CounterScreenEvent {} class IncrementCounterValue extends CounterScreenEvent {} class DecrementCounterValue extends CounterScreenEvent {} class GenerateRandomCounterValue extends CounterScreenEvent {} /// States class CounterScreenState extends Equatable { @override List<Object> get props => null; } class ShowLoadingCounterScreen extends CounterScreenState {} class ShowCounterValue extends CounterScreenState { @override List<Object> get props => [counterValue]; final int counterValue; ShowCounterValue(this.counterValue); } /// BLoC class CounterScreenBloc extends Bloc<CounterScreenEvent, CounterScreenState> { int counterValue = 0; @override CounterScreenState get initialState => ShowCounterValue(counterValue); @override Stream<CounterScreenState> mapEventToState(CounterScreenEvent event) async* { if (event is IncrementCounterValue) { this.counterValue++; yield ShowCounterValue(counterValue); } if (event is DecrementCounterValue) { this.counterValue--; yield ShowCounterValue(counterValue); } if (event is GenerateRandomCounterValue) { /// Showing Loading Screen yield ShowLoadingCounterScreen(); /// Created a repository called CounterRepo /// which is responsible for getting data counterValue = await CounterRepo().getRandomValue(); /// Showing the random number yield ShowCounterValue(counterValue); } } }
Important Thing To Know
Algo importante que debes saber Bloc
es si nextState == currentState
evalúa a true
, bloc
ignora la transición de estado.
Si no extiende su clase estatal como Equatable
, todos los estados desde los que llamará yield
será tratado como único.
Si eso se ajusta a sus requisitos, no debería preocuparse mucho, pero si existe la posibilidad de que se repitan estados uno tras otro, ¿por qué desperdiciar su rendimiento en la reconstrucción del mismo estado?
Por lo tanto, extendemos Equatable
y anular el método props
.
class ShowCounterValue extends CounterScreenState @override List<Object> get props => [counterValue]; final int counterValue; ShowCounterValue(this.counterValue);
Si no anula el método props
, tu Bloc
no sabrá cómo comparar las dos clases estatales y probablemente las tratará de la misma manera. Por lo tanto, no vería que se activara el nuevo estado.
6. Trabajemos en esa interfaz de usuario
Dado que estamos usando un Blocnamed
CounterScreenBloc
en nuestra interfaz de usuario named CounterScreen
, usamos un BlocProvider
y definimos qué se debe proporcionar con qué Bloc
.
Aquí está el código main.dart
completo .
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_counter_example/counter_screen/CounterScreenBloc.dart'; import 'counter_screen/CounterScreen.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Counter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: _buildCounterScreen(), ); } Widget _buildCounterScreen() => BlocProvider<CounterScreenBloc>( create: (context) => CounterScreenBloc(), child: CounterScreen()); }
Miremos adentro'counter_screen.dart’
Dentro de la función Build
de nuestro CounterScreen
, tenemos que inicializar el Bloc
. Del mismo modo, dentro de la función dispose, tenemos que cerrar nuestro Bloc
para evitar pérdidas de memoria.
Es una buena práctica eliminar el bloque en la función dispose () del mismo widget cuya build () puede haber usado para inicializar el bloque.
Aquí está el completo counter_screen.dart
:
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_counter_example/counter_screen/CounterScreenBloc.dart'; class CounterScreen extends StatefulWidget { CounterScreen({Key key, this.title}) : super(key: key); final String title; @override _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { CounterScreenBloc _counterScreenBloc; @override void dispose() { /// Closing the bloc in dispose to avoid memory leaks _counterScreenBloc.close(); super.dispose(); } @override Widget build(BuildContext context) { /// Initialize bloc in the build method _counterScreenBloc = BlocProvider.of<CounterScreenBloc>(context); return Scaffold( appBar: buildAppBar(), body: buildBody(context), ); } Widget buildBody(BuildContext context) { return BlocBuilder<CounterScreenBloc, CounterScreenState>( builder: (context, state) { print("HIT IT"); if (state is ShowLoadingCounterScreen) { print("triggered state"); /// Show Loading Screen return buildLoadingScreen(); } else if (state is ShowCounterValue) { print("SHOW COUNTER VALUE"); /// Show Counter Value return buildCounterScreen(state.counterValue); } else { print("ELSE"); /// Just returning an empty container return Container(); } }); } Widget buildCounterScreen(int counterValue) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( counterValue.toString(), style: Theme.of(context).textTheme.display1, ), Padding( padding: const EdgeInsets.only(top: 100), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), SizedBox( width: 50, ), FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: Icon(Icons.remove), ), SizedBox( width: 50, ), FloatingActionButton( onPressed: _randomCounter, tooltip: 'Random', child: Icon(Icons.shuffle), ) ], ), ) ], ); } void _incrementCounter() { _counterScreenBloc.add(IncrementCounterValue()); } void _randomCounter() { _counterScreenBloc.add(GenerateRandomCounterValue()); } void _decrementCounter() { _counterScreenBloc.add(DecrementCounterValue()); } AppBar buildAppBar() { return AppBar( title: Text("Counter Bloc Example"), ); } Widget buildLoadingScreen() { return Center(child: CircularProgressIndicator()); } }
¡Eso es! Deberías tener un proyecto en funcionamiento.
Eso es todo lo que tienes que saber para crear una aplicación Flutter
increíble con código limpio, todo gracias a Bloc
.
Nuevamente, el código repetitivo que ve es una inversión para mantener su código mantenible.
Además, debido a esto, no recomendaría que lo aplique en todas las pantallas, solo cuando hay muchos estados diferentes involucrados.
De lo contrario, Provider es otra excelente solución de administración de estado para funciones más simples en su aplicación.
Gracias por leer este artículo.
Añadir comentario