DEV Community

Marcelo Sebastián
Marcelo Sebastián

Posted on • Edited on

Manejo de estados en flutter, BLoC Pattern sin librerías

En esta guía se mostrará el uso del patrón BLoC sin hacer uso de librerías de terceros.

Manejo de estados en flutter, BLoC Pattern sin librerías

El código de esta guía se lo puede encontrar en GitHub.

GitHub logo marcedroid / Flutter-State-Management-Demos

Manejo de estados en Flutter

Importante

La rama bloc-no-libraries es la que tiene la implementación del patrón BLoC.
La rama master tiene el código base de la aplicación.


La aplicación cuenta con tres vistas:

  • User: En esta vista al cambiar los valores del campo de texto también se actualizará el título del AppBar, además se actualizará en nombre de usuario en la vista cart.
  • Catalog: En esta vista se muestra un listado de Items y al interactuar con cada uno de ellos se puede ver que el ícono para agregar/eliminar cambia, también se actualiza el contador de items del AppBar y el listado de items de la vista cart.
  • Cart: En esta vista se muestra un listado de los productos que se han seleccionado en la vista de catalog también se muestra el precio total y el nombre de usuario junto con las iniciales del mismo en un CircleAvatar.

Para tener el código ordenado se debe crear en la carpeta lib la carpeta blocs y dentro de esta carpeta crear catalog y user

  • blocs
    • catalog
    • user

Índice



Patrón BLoC para nombre de usuario

Crear tres archivos dentro de la carpeta blocs/user

  • user_event.dart: Archivo en el cual se declararán los eventos de patrón BLoC.
  • user_state.dart: Archivo que se encargará de la lógica para obtener, modificar o eliminar la data.
  • user_bloc.dart: Detectará el tipo de evento recibido y se encargará de responder a ellos.

user_event.dart

abstract class UserEvent {}

class OnChangeEvent extends UserEvent {
  final String value;

  OnChangeEvent(this.value);
}

class GetUsernameEvent extends UserEvent {}
Enter fullscreen mode Exit fullscreen mode

Es un archivo bastante simple, que podíamos haber incluido directamente en el archivo _bloc, pero de esta forma tenemos mucho mejor ordenado el código.

Explicación:

Primero es necesario definir los eventos, una de las formas más fáciles es hacerlo con clases.
Vamos a crear una clase abstracta base de la cual van a extender el resto de nuestros eventos de usuario.

abstract class UserEvent {}
Enter fullscreen mode Exit fullscreen mode

Luego vamos a definir el evento que se ejecutará al momento de hacer un cambio en el nombre de usuario.
Este va a recibir como parámetro un String.

class OnChangeEvent extends UserEvent {
  final String value;

  OnChangeEvent(this.value);
}
Enter fullscreen mode Exit fullscreen mode

Por último vamos a definir otro evento con el cual obtendremos el nombre de usuario actual, esto lo vamos a necesitar para actualizar la data entre pantallas o tomar el valor fuera de una vista.

class GetUsernameEvent extends UserEvent {}
Enter fullscreen mode Exit fullscreen mode

user_state.dart

class UserState {
  String _username = 'Guest';

  UserState._();
  static UserState _instance = UserState._();
  factory UserState() => _instance;

  String get username => _username;

  void onChange(value) {
    _username = value;
  }
}
Enter fullscreen mode Exit fullscreen mode

Explicación:

Crear una variable privada, en este caso quiero que el usuario por default sea Guest es por eso que no he dejado un string vacío.

String _username = 'Guest';
Enter fullscreen mode Exit fullscreen mode

Necesitamos tener una instancia que sea compartida y que solo pueda ser instanciada una vez (singleton).

// Definimos un constructor privado.
UserState._();

// Crear una instancia privada usando nuestro constructor.
static UserState _instance = UserState._();

// Crear una factory (método que genera las instancias)
factory UserState() => _instance;
Enter fullscreen mode Exit fullscreen mode

Crear un get para obtener en nombre de usuario.

String get username => _username;
Enter fullscreen mode Exit fullscreen mode

Por último crear el método para actualizar el nombre de usuario.

void onChange(value) {
  _username = value;
}
Enter fullscreen mode Exit fullscreen mode

user_bloc.dart

import 'dart:async';

import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/blocs/user/user_state.dart';

class UserBloc {
  UserState _userState = UserState();

  StreamController<UserEvent> _input = StreamController();
  StreamController<String> _output = StreamController();

  StreamSink<UserEvent> get sendEvent => _input.sink;
  Stream<String> get userStream => _output.stream;

  UserBloc() {
    _input.stream.listen(_onEvent);
  }

  void _onEvent(UserEvent event) {
    if (event is OnChangeEvent) {
      _userState.onChange(event.value);
    }

    _output.add(_userState.username);
  }

  void dispose() {
    _input.close();
    _output.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar dart:async ya que es requerido por StreamController, también importar el archivo de eventos y estado.

import 'dart:async';

import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/blocs/user/user_state.dart';
Enter fullscreen mode Exit fullscreen mode

Crear una variable privada de tipo UserState().

UserState _userState = UserState();
Enter fullscreen mode Exit fullscreen mode

Crear dos variables privadas de tipo StreamController().

StreamController<UserEvent> _input = StreamController();
StreamController<String> _output = StreamController();
Enter fullscreen mode Exit fullscreen mode

Exponer sink y stream mediante getters, esto es para que la vistas puedan enviar eventos y escuchar los cambios.

StreamSink<UserEvent> get sendEvent => _input.sink;
Stream<String> get userStream => _output.stream;
Enter fullscreen mode Exit fullscreen mode

Crear un constructor que escuche los eventos.

UserBloc() {
  _input.stream.listen(_onEvent);
}
Enter fullscreen mode Exit fullscreen mode

Crear un método privado _onEvent que se encargue de procesar los eventos que escucha el _input.stream.

void _onEvent(UserEvent event) {
// Si el evento es de tipo OnChangeEvent, ejecutar la función onChange de _userState.
  if (event is OnChangeEvent) {
    _userState.onChange(event.value);
  }

// Enviamos el nuevo valor utilizando el _output.
  _output.add(_userState.username);
}
Enter fullscreen mode Exit fullscreen mode

Liberar los recursos de los StreamController() cuando no se los necesite con un dispose.

void dispose() {
  _input.close();
  _output.close();
}
Enter fullscreen mode Exit fullscreen mode


Patrón BLoC para catálogo

Estos pasos son prácticamente los mismos que se hicieron para el patrón BLoC del nombre de usuario.

Crear tres archivos dentro de la carpeta blocs/catalog

  • catalog_event.dart: Archivo en el cual se declararán los eventos de patrón BLoC.
  • catalog_state.dart: Archivo que se encargará de la lógica para obtener, modificar o eliminar la data.
  • catalog_bloc.dart: Detectará el tipo de evento recibido y se encargará de responder a ellos.

catalog_event.dart

import 'package:state_management/models/item_model.dart';

abstract class CatalogEvent {}

class AddCatalogItemEvent extends CatalogEvent {
  final ItemModel item;

  AddCatalogItemEvent(this.item);
}

class RemoveCatalogItemEvent extends CatalogEvent {
  final ItemModel item;

  RemoveCatalogItemEvent(this.item);
}

class GetCatalogEvent extends CatalogEvent {}
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar item_model.dart ya que este BLoC va a usar una lista de ItemModel.

import 'package:state_management/models/item_model.dart';
Enter fullscreen mode Exit fullscreen mode

Crear una clase abstracta de la cual van a extender los otros eventos.

abstract class CatalogEvent {}
Enter fullscreen mode Exit fullscreen mode

Definir un evento para agregar un item al carrito, recibe como parámetro un ItemModel.

class AddCatalogItemEvent extends CatalogEvent {
  final ItemModel item;

  AddCatalogItemEvent(this.item);
}
Enter fullscreen mode Exit fullscreen mode

Definir un evento para eliminar un item del carrito, recibe como parámetro un ItemModel.

class RemoveCatalogItemEvent extends CatalogEvent {
  final ItemModel item;

  RemoveCatalogItemEvent(this.item);
}
Enter fullscreen mode Exit fullscreen mode

Definir un evento que se ejecutará para obtener el listado del catálogo.

class GetCatalogEvent extends CatalogEvent {}
Enter fullscreen mode Exit fullscreen mode

catalog_state.dart

import 'package:state_management/models/item_model.dart';

class CatalogState {
  List<ItemModel> _catalog = [];

  CatalogState._();
  static CatalogState _instance = CatalogState._();
  factory CatalogState() => _instance;

  List<ItemModel> get catalog => _catalog;

  void addToCatalog(ItemModel itemModel) {
    _catalog.add(itemModel);
  }

  void removeFromCatalog(ItemModel itemModel) {
    _catalog.remove(itemModel);
  }
}
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar item_model.dart ya que este BLoC va a usar una lista de ItemModel.

import 'package:state_management/models/item_model.dart';
Enter fullscreen mode Exit fullscreen mode

Crear una variable privada que va a contener una lista de objetos ItemModel, en este caso se inicial la lista vacía.

List<ItemModel> _catalog = [];
Enter fullscreen mode Exit fullscreen mode

Necesitamos tener una instancia que sea compartida y que solo pueda ser instanciada una vez (singleton).

// Definimos un constructor privado.
CatalogState._();

// Crear una instancia privada usando nuestro constructor.
static CatalogState _instance = CatalogState._();

// Crear la factory (método que genera las instancias)
factory CatalogState() => _instance;
Enter fullscreen mode Exit fullscreen mode

Crear un getter para obtener el listado de ItemModel

List<ItemModel> get catalog => _catalog;
Enter fullscreen mode Exit fullscreen mode

Crear el método que agrega un item al carrito.

void addToCatalog(ItemModel itemModel) {
  _catalog.add(itemModel);
}
Enter fullscreen mode Exit fullscreen mode

Crear el método que elimina un item del carrito.

void removeFromCatalog(ItemModel itemModel) {
  _catalog.remove(itemModel);
}
Enter fullscreen mode Exit fullscreen mode

catalog_bloc.dart

import 'dart:async';

import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/blocs/catalog/catalog_state.dart';
import 'package:state_management/models/item_model.dart';

class CatalogBloc {
  CatalogState _catalogState = CatalogState();

  StreamController<CatalogEvent> _input = StreamController();
  StreamController<List<ItemModel>> _output =
      StreamController<List<ItemModel>>.broadcast();

  StreamSink<CatalogEvent> get sendEvent => _input.sink;
  Stream<List<ItemModel>> get catalogStream => _output.stream;

  CatalogBloc() {
    _input.stream.listen(_onEvent);
  }

  void _onEvent(CatalogEvent event) {
    if (event is AddCatalogItemEvent) {
      _catalogState.addToCatalog(event.item);
    } else if (event is RemoveCatalogItemEvent) {
      _catalogState.removeFromCatalog(event.item);
    }

    _output.add(_catalogState.catalog);
  }

  void dispose() {
    _input.close();
    _output.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar dart:async ya que es requerido por StreamController, también importar el archivo de eventos, estado e ItemModal.

import 'dart:async';

import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/blocs/catalog/catalog_state.dart';
import 'package:state_management/models/item_model.dart';
Enter fullscreen mode Exit fullscreen mode

Crear una variable privada de tipo CatalogState().

CatalogState _catalogState = CatalogState();
Enter fullscreen mode Exit fullscreen mode

Crear dos variables provadas de tipo StreamController().
En este caso el _output va a ser escuchado en más de un lugar de la vista, por lo cual debe ser un broadcast.
StreamController<List<ItemModel>>.broadcast();

StreamController<CatalogEvent> _input = StreamController();
StreamController<List<ItemModel>> _output = StreamController<List<ItemModel>>.broadcast();
Enter fullscreen mode Exit fullscreen mode

Exponer sink y stream mediante getters, esto es para que la vistas puedan enviar eventos y escuchar los cambios.

StreamSink<CatalogEvent> get sendEvent => _input.sink;
Stream<List<ItemModel>> get catalogStream => _output.stream;
Enter fullscreen mode Exit fullscreen mode

Crear un constructor que escuche los eventos.

CatalogBloc() {
  _input.stream.listen(_onEvent);
}
Enter fullscreen mode Exit fullscreen mode

Crear un método privado _onEvent que se encargue de procesar los eventos que escucha el _input.stream.

void _onEvent(CatalogEvent event) {
  if (event is AddCatalogItemEvent) {
// Si el evento es de tipo AddCatalogItemEvent, ejecutar la función addToCatalog de _catalogState.
    _catalogState.addToCatalog(event.item);
  } else if (event is RemoveCatalogItemEvent) {
// Si el evento es de tipo RemoveCatalogItemEvent, ejecutar la función removeFromCatalog de _catalogState.
    _catalogState.removeFromCatalog(event.item);
  }

  _output.add(_catalogState.catalog);
}
Enter fullscreen mode Exit fullscreen mode

Liberar los recursos de los StreamController() cuando no se los necesite con un dispose.

void dispose() {
  _input.close();
  _output.close();
}
Enter fullscreen mode Exit fullscreen mode


Vistas

En esta sección se mostrará los cambios necesarios a realizar para que las vistas se actualicen usando el patrón BLoC.


user.dart

...

import 'package:state_management/blocs/user/user_bloc.dart';
import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/blocs/user/user_state.dart';

...

TextEditingController _textEditingController;
UserBloc _userBloc = UserBloc();

@override
void initState() {
  super.initState();

  _textEditingController = TextEditingController(text: UserState().username);
  _userBloc.sendEvent.add(GetUsernameEvent());
}

@override
void dispose() {
  _userBloc.dispose();
  super.dispose();
}

...

title: StreamBuilder<String>(
    stream: _userBloc.userStream,
    builder: (context, snapshot) {
      return Text('User - ${snapshot.data}');
    }),

...

onChanged: (value) {
  _userBloc.sendEvent.add(OnChangeEvent(value));
},

...
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar los archivos event, stats y bloc de usuario.

import 'package:state_management/blocs/user/user_bloc.dart';
import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/blocs/user/user_state.dart';
Enter fullscreen mode Exit fullscreen mode

Al sobre escribir initState() se debe colocar el nuevo código después de super.initState();
Al sobre escribir dispose() se debe colocar el nuevo código antes de super.dispose();

TextEditingController _textEditingController;
// Instanciar el user bloc
UserBloc _userBloc = UserBloc();

// Sobrescribir `initState` y enviar el evento `GetUsernameEvent` para actualizar el nombre de usuario al cambiar de página.
@override
void initState() {
  super.initState();

// Asignarle un valor inicial al TextEditingController, en este caso el nombre de usuario.
  _textEditingController = TextEditingController(text: UserState().username);
  _userBloc.sendEvent.add(GetUsernameEvent());
}

// Sobrescribir `dispose` para librerar los recursos del BLoC cuando ya no se usen.
@override
void dispose() {
  _userBloc.dispose();
  super.dispose();
}
Enter fullscreen mode Exit fullscreen mode

Envolver el Text widget del AppBar en un StreamBuilder, de esta forma se actualizará de manera automática.

// Es necesario asignar el tipo de valor que se va a actualizar, en este caso String
title: StreamBuilder<String>(
// El stream que se pasa hace referencia a _output.stream
    stream: _userBloc.userStream,
    builder: (context, snapshot) {
// snapshot.data es el valor que se actualiza.
      return Text('User - ${snapshot.data}');
    }),
Enter fullscreen mode Exit fullscreen mode

catalog.dart

...

import 'package:state_management/blocs/catalog/catalog_bloc.dart';
import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/models/item_model.dart';

...

CatalogBloc _catalogBloc = CatalogBloc();

@override
void initState() {
  super.initState();
  _catalogBloc.sendEvent.add(GetCatalogEvent());
}

@override
void dispose() {
  _catalogBloc.dispose();
  super.dispose();
}

...

child: CircleAvatar(
  child: StreamBuilder<List<ItemModel>>(
      initialData: [],
      stream: _catalogBloc.catalogStream,
      builder: (context, snapshot) {
        return Text(
          '${snapshot.data.length}',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 14.0,
          ),
        );
      }),

...

trailing: IconButton(
  onPressed: () {
    if (items[index].addedToCart) {
      widget._catalogBloc.sendEvent.add(
        RemoveCatalogItemEvent(items[index]),
      );
    } else {
      widget._catalogBloc.sendEvent.add(
        AddCatalogItemEvent(items[index]),
      );
    }
  },
),

...
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar los archivos bloc y state de catalog además de item_model.

import 'package:state_management/blocs/catalog/catalog_bloc.dart';
import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/models/item_model.dart';
Enter fullscreen mode Exit fullscreen mode

Al sobre escribir initState() se debe colocar el nuevo código después de super.initState();
Al sobre escribir dispose() se debe colocar el nuevo código antes de super.dispose();

// Instanciar el catalog bloc
CatalogBloc _catalogBloc = CatalogBloc();

// Sobrescribir `initState` y enviar el evento `GetCatalogEvent` para actualizar el catálogo al cambiar de página.
@override
void initState() {
  super.initState();
  _catalogBloc.sendEvent.add(GetCatalogEvent());
}

// Sobrescribir `dispose` para librerar los recursos del BLoC cuando ya no se usen.
@override
void dispose() {
  _catalogBloc.dispose();
  super.dispose();
}
Enter fullscreen mode Exit fullscreen mode

Envolver el Text widget en un StreamBuilder que contiene el número de items seleccionados.

child: CircleAvatar(

// En este caso el StreamBuilder es de tipo <List<ItemModel>>
  child: StreamBuilder<List<ItemModel>>(

// Se coloca una Lista vacía como data inicial para evitar errores al momento de hacer snapshot.data.length
      initialData: [],

// Pasar como stream el catalogStream del archivo catalog_bloc
      stream: _catalogBloc.catalogStream,
      builder: (context, snapshot) {
        return Text(

// snapshot.data.length es para obtener el número de items seleccionado
          '${snapshot.data.length}',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 14.0,
          ),
        );
      }),
Enter fullscreen mode Exit fullscreen mode

En este caso se ve widget._catalogBloc en lugar de solo
_catalogBloc, esto se debe a que se pasa _catalogBloc como atributo.

trailing: IconButton(
  onPressed: () {
    if (items[index].addedToCart) {

// Se envía el evento RemoveCatalogItemEvent y se pasa como parámetro el item actual
      widget._catalogBloc.sendEvent.add(
        RemoveCatalogItemEvent(items[index]),
      );
    } else {

// Se envía el evento AddCatalogItemEvent y se pasa como parámetro el item actual
      widget._catalogBloc.sendEvent.add(
        AddCatalogItemEvent(items[index]),
      );
    }
  },
),
Enter fullscreen mode Exit fullscreen mode

cart.dart

...

import 'package:state_management/blocs/catalog/catalog_bloc.dart';
import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/blocs/user/user_bloc.dart';
import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/models/item_model.dart';

...

UserBloc _userBloc = UserBloc();
CatalogBloc _catalogBloc = CatalogBloc();

@override
void initState() {
  super.initState();
  _userBloc.sendEvent.add(GetUsernameEvent());
  _catalogBloc.sendEvent.add(GetCatalogEvent());
}

@override
void dispose() {
  _userBloc.dispose();
  _catalogBloc.dispose();
  super.dispose();
}

...

Expanded(
  child: StreamBuilder<List<ItemModel>>(
    initialData: [],
    stream: _catalogBloc.catalogStream,
    builder: (context, snapshot) {
      return ListView.builder(
        itemCount: snapshot.data.length,
        itemBuilder: (context, index) => ListTile(
          title: Text(
            '${snapshot.data[index].name}'.toUpperCase(),
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
          trailing: Text(
            '\$${snapshot.data[index].price.toStringAsFixed(2)}',
          ),
        ),
      );
    },
  ),
),

...

StreamBuilder<String>(
  stream: _userBloc.userStream,
  builder: (context, snapshot) {
    return Column(
      children: [
        CircleAvatar(
          child: Text(
            getInitials('${snapshot.data}').toUpperCase(),
          ),
        ),
        Text(
          '${snapshot.data}',
          style: TextStyle(
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    );
  },
),

...

StreamBuilder<List<ItemModel>>(
  initialData: [],
  stream: _catalogBloc.catalogStream,
  builder: (context, snapshot) {
    return Text(
      '\$${formatTotal(snapshot.data)}',
      style: TextStyle(
        fontSize: 20.0,
        fontWeight: FontWeight.bold,
      ),
    );
  },
),

...
Enter fullscreen mode Exit fullscreen mode

Explicación:

Importar los archivos bloc y state de user y catalog, además de item_model.

import 'package:state_management/blocs/catalog/catalog_bloc.dart';
import 'package:state_management/blocs/catalog/catalog_event.dart';
import 'package:state_management/blocs/user/user_bloc.dart';
import 'package:state_management/blocs/user/user_event.dart';
import 'package:state_management/models/item_model.dart';
Enter fullscreen mode Exit fullscreen mode

Al sobre escribir initState() se debe colocar el nuevo código después de super.initState();
Al sobre escribir dispose() se debe colocar el nuevo código antes de super.dispose();

// Instanciar user y catalog bloc
UserBloc _userBloc = UserBloc();
CatalogBloc _catalogBloc = CatalogBloc();

// Sobrescribir `initState` y enviar el evento `GetUsernameEvent` `GetCatalogEvent` para actualizar el nombre y catálogo al cambiar de página.
@override
void initState() {
  super.initState();
  _userBloc.sendEvent.add(GetUsernameEvent());
  _catalogBloc.sendEvent.add(GetCatalogEvent());
}

// Sobrescribir `dispose` para librerar los recursos del BLoC cuando ya no se usen.
@override
void dispose() {
  _userBloc.dispose();
  _catalogBloc.dispose();
  super.dispose();
}
Enter fullscreen mode Exit fullscreen mode

Envolver el ListView widget en un StreamBuilder para actualizar el title y trailing del ListTile.

Expanded(

// En este caso el StreamBuilder es de tipo <List<ItemModel>>
  child: StreamBuilder<List<ItemModel>>(

// Se coloca una Lista vacía como data inicial para evitar errores al momento de hacer snapshot.data.length
    initialData: [],

// Pasar como stream el catalogStream del archivo catalog_bloc
    stream: _catalogBloc.catalogStream,
    builder: (context, snapshot) {
      return ListView.builder(

// snapshot.data.length es para obtener el número de items seleccionado
        itemCount: snapshot.data.length,
        itemBuilder: (context, index) => ListTile(
          title: Text(

// snapshot.data[index].name nombre del producto.
            '${snapshot.data[index].name}'.toUpperCase(),
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
          trailing: Text(

// snapshot.data[index].price.toStringAsFixed(2) precio del producto con dos decimales
            '\$${snapshot.data[index].price.toStringAsFixed(2)}',
          ),
        ),
      );
    },
  ),
),
Enter fullscreen mode Exit fullscreen mode

Envolver el Column widget en un StreamBuilder para actualizar el nombre de usuario.

// En este caso el StreamBuilder es de tipo <String>
StreamBuilder<String>(

// Pasar como stream el userStream del archivo user_bloc
  stream: _userBloc.userStream,
  builder: (context, snapshot) {
    return Column(
      children: [
        CircleAvatar(
          child: Text(

// Muestra las iniciales del nombre de usuario
            getInitials('${snapshot.data}').toUpperCase(),
          ),
        ),
        Text(

// Muestra el nombre de usuario
          '${snapshot.data}',
          style: TextStyle(
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    );
  },
),
Enter fullscreen mode Exit fullscreen mode

Envolver el Text widget en un StreamBuilder para actualizar el precio total de los items seleccionados.

// En este caso el StreamBuilder es de tipo <List<ItemModel>>
StreamBuilder<List<ItemModel>>(

// Se coloca una Lista vacía como data inicial para evitar errores al momento de hacer snapshot.data
  initialData: [],

// Pasar como stream el catalogStream del archivo catalog_bloc
  stream: _catalogBloc.catalogStream,
  builder: (context, snapshot) {
    return Text(

// Muestra el precio total de los items seleccionados
      '\$${formatTotal(snapshot.data)}',
      style: TextStyle(
        fontSize: 20.0,
        fontWeight: FontWeight.bold,
      ),
    );
  },
),
Enter fullscreen mode Exit fullscreen mode


Eso es todo, recuerda que código de esta guía se lo puede encontrar en GitHub.

GitHub logo marcedroid / Flutter-State-Management-Demos

Manejo de estados en Flutter

Top comments (2)

Collapse
 
maiconcrespo profile image
Maicon Crespo

Great Man, la mejor explicacion que he encontrado hasta ahora para entender bloc, para iniciantes como yo, muy simple , claro y dinamico, com mas practicas seguro que aprendo, y con el apoyo de esta page, y otra sin librerias...wow...es lo mejor. jajaaj

Collapse
 
jovanymezura profile image
Raul Jovany

Me ayudo mucho esta guía gracias por compartir con esto empiezo a poner los pies sobre la tierra acerca del patron bloc y flutter. Con la practica seguro que todo se aclarara. Saludos