Este artículo surgió de la publicación " Patrón BLoC con un ejemplo simple " donde descubrimos qué es este patrón y cómo aplicarlo en un ejemplo de contador simple clásico.
De acuerdo con los comentarios y para mi mejor comprensión, decidí intentar escribir una aplicación en la que se recibieran respuestas a las preguntas:
- Cómo transferir el estado de la clase en la que se encuentra el BloC en toda la aplicación
- Cómo escribir pruebas para este patrón
- (pregunta adicional) Cómo mantener el estado de los datos entre los lanzamientos de aplicaciones mientras se mantiene dentro del patrón BLoC
A continuación se muestra una animashka del ejemplo resultante, y debajo del corte hay un informe :)
Y al final del artículo, un problema interesante es cómo modificar la aplicación para aplicar el operador Debounce desde el patrón ReactiveX (más precisamente, reactiveX es una extensión del patrón Observer)

Descripción de la aplicación y código base
No relacionado con BLoC y Proveedor
- La aplicación tiene botones + - y el deslizamiento que duplica estos botones funciona
- Animación realizada a través de flutter mixin incorporado - TickerProviderStateMixin
Vinculado a BLoC y Proveedor
- Dos pantallas: en la primera que deslizamos, en la segunda se muestran los cambios del contador
- Escribimos el estado en el almacenamiento permanente del teléfono (iOS y Android, paquete https://pub.dev/packages/shared_preferences )
- Escribir y leer información del almacenamiento persistente es asíncrono, también lo hacemos a través de BLoC
Estamos escribiendo una solicitud
Como se deduce de la definición del patrón BLoC, nuestra tarea es eliminar toda la lógica de los widgets y trabajar con datos a través de una clase en la que todas las entradas y salidas son Streams.
Al mismo tiempo, dado que la clase en la que se encuentra BLoC se usa en diferentes pantallas, necesitamos transferir el objeto creado a partir de esta clase en toda la aplicación.
Existen diferentes métodos para esto, a saber:
- Pasando a través de los constructores de clase, el llamado estado de elevación . No lo usaremos, ya que resulta ser muy confuso, entonces no rastreemos las transferencias estatales.
- Realice desde la clase donde tenemos BLoC singleton e impórtelo donde lo necesitemos. Es simple y conveniente, pero, desde mi punto de vista puramente personal, complica el constructor de la clase y confunde un poco la lógica.
- Use el paquete Proveedor, que recomienda el equipo de Flutter para la gestión del estado. Ver el video
En este ejemplo, usaremos el Proveedor, para dar un ejemplo de todos los métodos que no tenían suficiente fuerza :)
Estructura general
Entonces tenemos una clase
class SwipesBloc { // some stuff }
y para que un objeto creado a partir de esta clase sea accesible a través del árbol de widgets, nosotros, en cierto nivel de widgets de aplicaciones, definimos un proveedor de esta clase. Lo hice en la parte superior del árbol de widgets, pero es mejor hacerlo en el nivel más bajo posible.
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<SwipesBloc>(create: (_) => SwipesBloc()), ], child: MaterialApp( title: 'Swipe BLoC + Provider',
Después de agregar este hermoso diseño a cualquier widget en la parte inferior del árbol, tenemos disponible un objeto con todos los datos. Detalles sobre cómo trabajar con el proveedor aquí y aquí .
A continuación, debemos asegurarnos de que al hacer clic en el botón o deslizar todos los datos se transfieran al Stream y, luego, en todas las pantallas, los datos se actualicen desde el mismo Stream.
Clase para BLoC
Para hacer esto, creamos una clase BLoC en la que describimos no solo las transmisiones, sino también la recepción y el registro del estado del almacenamiento permanente del teléfono.
import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SwipesBloc { Future<SharedPreferences> prefs = SharedPreferences.getInstance(); int _counter; SwipesBloc() { prefs.then((val) { if (val.get('count') != null) { _counter = val.getInt('count') ?? 1; } else { _counter = 1; } _actionController.stream.listen(_changeStream); _addValue.add(_counter); }); } final _counterStream = BehaviorSubject<int>.seeded(1); Stream get pressedCount => _counterStream.stream; void get resetCount => _actionController.sink.add(null); Sink get _addValue => _counterStream.sink; StreamController _actionController = StreamController(); StreamSink get incrementCounter => _actionController.sink; void _changeStream(data) async { if (data == null) { _counter = 1; } else { _counter = _counter + data; } _addValue.add(_counter); prefs.then((val) { val.setInt('count', _counter); }); } void dispose() { _counterStream.close(); _actionController.close(); } }
Si miramos cuidadosamente esta clase, veremos que:
- Cualquier propiedad disponible externamente son las entradas y salidas de Streams.
- En la primera ejecución del diseñador, intentamos obtener datos del almacenamiento permanente del teléfono.
- Convenientemente grabado en el almacenamiento permanente del teléfono.
Pequeñas tareas para una mejor comprensión:
- Eliminar un fragmento de código de .then del constructor es más bonito para crear un método separado.
- Intente implementar esta clase sin un proveedor como Singleton
Recibir y transmitir datos en la aplicación.
Ahora necesitamos transferir datos a Stream al hacer clic en los botones o deslizar y obtener estos datos en la tarjeta y en una pantalla separada.
Hay diferentes opciones de cómo hacer esto, elegí la clásica, envolvemos aquellas partes del árbol donde necesita recibir / transferir datos al consumidor
return Scaffold( body: Consumer<SwipesBloc>( builder: (context, _swipesBloc, child) { return StreamBuilder<int>( stream: _swipesBloc.pressedCount, builder: (context, snapshot) { String counterValue = snapshot.data.toString(); return Stack( children: <Widget>[ Container(
Bueno, entonces obteniendo datos
_swipesBloc.pressedCount,
Transferencia de datos
_swipesBloc.incrementCounter.add(1);
Eso es todo, tenemos un código claro y extensible en las reglas del patrón BLoC.
Ejemplo de trabajo
Pruebas
Puedes probar widgets, puedes hacer mokas, puedes e2e.
Pondremos a prueba los widgets y ejecutaremos la aplicación con una comprobación de cómo funciona el aumento de contador. Información sobre pruebas aquí y aquí .
Prueba de widgets
Si tuviéramos datos sincrónicos, podríamos probar todo con widgets. En nuestro caso, solo podemos verificar cómo se crearon los widgets y cómo fue la inicialización.
El código está aquí , en el código hay intentos de verificar el aumento del contador después de hacer clic; da un error, ya que los datos pasan por BLoC.
Para ejecutar la prueba, use el comando
flutter test
Pruebas de integración
En esta opción de prueba, la aplicación se ejecuta en el emulador, podemos presionar botones, deslizar y verificar lo que sucedió como resultado.
Para hacer esto, creamos 2 archivos:
test_driver / app.dart
test_driver / app_test.dart
En el primero, conectamos lo que se necesita, y en el segundo, directamente las pruebas. Por ejemplo, hice los controles:
- Estado inicial
- Incrementos de contador después de presionar un botón
import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group( 'park-flutter app', () { final counterTextFinder = find.byValueKey('counterKey'); final buttonFinder = find.byValueKey('incrementPlusButton'); FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { driver.close(); } }); test('test init value', () async { expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); test('test + 1 value after tapped', () async { await driver.tap(buttonFinder); // Then, verify the counter text is incremented by 1. expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); }, ); }
Codigo alli
Para ejecutar la prueba, use el comando
flutter drive --target=test_driver/app.dart
Desafío
Solo para profundizar tu comprensión. En aplicaciones modernas (sitios), la función Debounce de ReactiveX se usa a menudo.
Por ejemplo:
- Se ingresa una palabra en la barra de búsqueda y una pista se cae solo cuando la brecha entre el conjunto de letras es más de 2 segundos
- Cuando se colocan Me gusta, puede hacer clic 10 veces por segundo; la escritura en la base de datos ocurrirá si la brecha en los clics fue más de 2-3 segundos
- ... etc.
Tarea: hacer que el dígito cambie solo si transcurren más de 2 segundos entre las pulsaciones de + o -. Para hacer esto, edite solo la clase BLoC, el resto del código debe permanecer igual.
Eso es todo Si algo está torcido o está mal, corríjalo aquí o en github , intente lograr el ideal :)
Buena codificación para todos!