Flutter BlocProvider consumption

Issue

I’m implementing a BLoC pattern for state management in my Fluter application. As I’m new in Flutter and BLoC particularly I’m evolving its usage gradually.
For new I use BLoC to communicate between two pages. One page sends an asset to the BLoC and navigates to details page. The details page uses StreamBuilder to read from the BLoC and build page with according data:

AppWidget:

Widget build(BuildContext context) => MultiProvider(
          providers: [
            BlocProvider(create: (context) => AssetBloc())
...

Requesting page

  _onAssetMenuAction(BuildContext context, AssetMenu value, Asset asset) {
    switch (value) {
      case AssetMenu.validate:
        var bloc = BlocProvider.of<AssetBloc>(context);
        bloc.validate(asset);
        Navigator.push(context,
            MaterialPageRoute(builder: (context) => ValidateAssetPage()));
        break;
    }

Validation page

  Widget build(BuildContext context) {
    var bloc = BlocProvider.of<AssetBloc>(context);
    Logger.root.info("Building validation page");
    return StreamBuilder<AssetValidation>(
        stream: bloc.outValidation,
        builder: (context, snapshot) => snapshot.hasData
            ? QrImage.withQr(qr: snapshot.data!.qr)
            : Text("No QR"));
  }

BLoC

class AssetBloc extends BlocBase {
  //
  // Stream to handle the validation request outcome
  //
  StreamController<AssetValidation> _validationController =
      StreamController<AssetValidation>.broadcast();
  StreamSink<AssetValidation> get _inValidation => _validationController.sink;
  Stream<AssetValidation> get outValidation => _validationController.stream;

  //
  // Stream to handle the validation request
  //
  StreamController<Asset> _validateController = StreamController<Asset>();
  void Function(Asset) get validate => _validateController.sink.add;

  //
  // Constructor
  //
  AssetBloc([state]) : super(state) {
    _validateController.stream.listen(_handleLogic);
  }

  void _handleLogic(Asset asset) {
    _inValidation.add(AssetValidation.create(asset));
    Logger.root.finest("AssetValidation instance is sent to stream");
  }

  void dispose() {
    _validateController.close();
    _validationController.close();
  }
}

The problem I have is I’m getting "No QR". According to logs I see following sequence of actions:

new AssetValidation.create(): Validating asset Instance of ‘Asset’

AssetBloc._handleLogic(): AssetValidation instance is sent to stream

ValidateAssetPage.build(): Building validation page

So at the moment of validation page building the validation result data should be in the stream but it seems they are not.

Unit tests of AssetBloc work as expected. So I suspect it should be related to StreamBuilder in validation page.

Solution

The StreamBuilder just shows you the last value of the stream whether the StreamBuilder was present on the current deployed widget when the stream was updated. So, if you add a new value to the stream, but the StreamBuilder is not on the current deployed widget, and, after that, you deploy the widget with the StreamBuilder, it’s very likely that it won’t show the updated data (in fact it shows empty data). I know, it’s weird, i have the same problem when i like to use streams in that way. So, instead, i recommend you to use ValueListenable on the bloc and ValueListenableBuilder on the widget. It’s very useful for that cases.

Another thing to point out is that if you’re going to use just streams for the state management, it’s better to use another state manager type such as provider or singleton. The reason is that, the right way to use bloc (the way you take advantage of the power of bloc) is using just the method add() for the events and logic, and using the established bloc State clases to show and update the data with the BlocBuilder on the widget.

Answered By – Jhonatan

Answer Checked By – Senaida (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *