flutterstateblocstream-builderinherited-widget

Flutter Bloc A adds stream in Bloc B but this stream will not render in UI via StreamBuilder


I'm trying to make the shift to a bloc / provider architecture for a new flutter project. I've been troubleshooting a problem for a while now where one bloc's streams will render on the UI via StreamBuilder but the other bloc will not. Before I continue on I'd love (and need to) to learn why.

I have two blocs, the first is ble_service. I can call functions from the UI to this bloc (app.dart lines 60-72) connecting to a device and rendering what the characteristic returns in the UI via StreamBuilder (line 98). Simply rendering the returned json payload from the ble device in the UI. This updates quite frequently multiple times a second.

My plan was to have a parser bloc (bleBeltNotifyParser_bloc) that would parse the incoming json payload from the ble_service and then stream from there UI. In ble_service I pass the json payload into parserInputSink, a stream from the Parser Bloc (ble_service line 99). In bleBeltNotifyParser.bloc I'm listening for it on line 21 and pass it to aTest2() where I planned to parse it. I stopped here because I tried to render this data on the UI (app.dart line 108) but no matter what different combination of passing data into the parserInputController the UI only renders the seed data. I confirmed the stream is getting data by printing it on line 28.

I've also confirmed I can get to the Parser bloc from the UI by very simply dropping some data into the stream via buttons (lines 73 & 80). When those buttons are pressed, that data is added to the stream and the UI updates as expected.

Why does StreamBuilder work for blue_service and not bleBeltNotifyParser_bloc? I also tried creating the stream in the service and then listening to it in the parser. No luck their either.

My main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  /// Starting here, everything is used regardless of dependencies
  var blocProvider = BlocProvider(
    bleBeltNotifyParserBloc: BleBeltNotifyParserBloc(),
    bleService: BleService(),

  );
  
  runApp(
    AppStateContainer(
      blocProvider: blocProvider,
      child:  App(),
    ),
  );
}

My AppState

import 'package:flutter/material.dart';
import 'package:flutterappbelt3/main.dart';
import 'package:flutterappbelt3/blocs/ble_service.dart';
import 'package:flutterappbelt3/blocs/bleBeltNotifyParser_bloc.dart';

class AppStateContainer extends StatefulWidget {
  final Widget child;
  final BlocProvider blocProvider;
  const AppStateContainer({
    Key key,
    @required this.child,
    @required this.blocProvider,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => AppState();

  static AppState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(_AppStoreContainer) as _AppStoreContainer).appData;
  }
}

class AppState extends State<AppStateContainer> {
  BlocProvider get blocProvider => widget.blocProvider;

  @override
  Widget build(BuildContext context) {
    return _AppStoreContainer(
      appData: this,
      blocProvider: widget.blocProvider,
      child: widget.child,
    );
  }

  void dispose() {
    super.dispose();
  }

}

class _AppStoreContainer extends InheritedWidget {
  final AppState appData;
  final BlocProvider blocProvider;

  _AppStoreContainer({
    Key key,
    @required this.appData,
    @required child,
    @required this.blocProvider,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(_AppStoreContainer oldWidget) => oldWidget.appData != this.appData;
}

class BlocProvider {
  BleBeltNotifyParserBloc bleBeltNotifyParserBloc = BleBeltNotifyParserBloc();
  BleService bleService;

  BlocProvider({
    @required this.bleBeltNotifyParserBloc,
    @required this.bleService,

  });
}

My app.dart

import 'package:flutter/material.dart';
import 'package:flutterappbelt3/blocs/bleBeltNotifyParser_bloc.dart';
import 'blocs/app_state.dart';
import 'blocs/ble_service.dart';

class App extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Position App',
      theme: new ThemeData(
        primarySwatch: Colors.red,
      ),
      home: PositionApp(),
    );
  }
}

class PositionApp extends StatefulWidget {
  @override
  _PositionAppState createState() => _PositionAppState();
}

class _PositionAppState extends State<PositionApp> {

  BleService _service = BleService();
  BleBeltNotifyParserBloc _bloc = BleBeltNotifyParserBloc();

  @override
  void initState() {
    super.initState();
     //_service.startScan();
    //_service.bleNotifyFromBelt.listen((String data) {_bloc.parserInputSink.add(data);});
  }

  @override
  Widget build(BuildContext context) {
    //SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
    BleService _bleServiceBloc = AppStateContainer.of(context).blocProvider.bleService;
    BleBeltNotifyParserBloc _bleBeltNotifyParserBloc = AppStateContainer.of(context).blocProvider.bleBeltNotifyParserBloc;
    return Scaffold(
      appBar: AppBar(
        title: Text("Position"),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Container(
              child: Text("hello"),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleServiceBloc.startScan();
              },
                child: Text("Scan & Connect"),
              ),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleServiceBloc.discoverServices();
              },
                child: Text("discover services"),
              ),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleServiceBloc.disconnectFromDevice();
              },
                child: Text("disconnect"),
              ),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleBeltNotifyParserBloc.addToParserController1();
              },
                child: Text("Parser One"),
              ),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleBeltNotifyParserBloc.addToParserController2();
              },
                child: Text("Parser Two"),
              ),
            ),
            Container(
              child: FlatButton(onPressed: () {
                _bleBeltNotifyParserBloc.aTest();
              },
                child: Text("aTest"),
              ),
            ),
            Container(
                child: StreamBuilder(
                    initialData: "0",
                    stream: _bleServiceBloc.bleNotifyFromBelt,
                    builder: (BuildContext context, AsyncSnapshot snapshot) {
                      if(snapshot.data == null) return CircularProgressIndicator();
                      else return Text(
                        snapshot.data.toString(),
                        style: TextStyle(fontSize: 14.0, color: Colors.black),
                        textAlign: TextAlign.center,
                      );}
                ),
              ),
            Container(
              child: StreamBuilder(
                  initialData: "0",
                  stream: _bleServiceBloc.bleStatusFromBelt,
                  builder: (BuildContext context, AsyncSnapshot snapshot) {
                    if(snapshot.data == null) return CircularProgressIndicator();
                    else return Text(
                      snapshot.data.toString(),
                      style: TextStyle(fontSize: 14.0, color: Colors.black),
                      textAlign: TextAlign.center,
                    );}
              ),
            ),
          ],
        ),
      ),
    );
  }
}

My ble_service.dart


import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'dart:convert' show utf8;
import 'package:rxdart/rxdart.dart';
import 'package:flutterappbelt3/blocs/bleBeltNotifyParser_bloc.dart';

class BleService {

//BleBeltNotifyParserBloc _bloc = BleBeltNotifyParserBloc();

  final String TARGET_DEVICE_NAME = "ESP32";
  final String SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
  final String NOTIFY_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
  final String WRITE_UUID = "724b0547-3747-4c00-9710-5305a020018f";
  FlutterBlue flutterBlue = FlutterBlue.instance;
  StreamSubscription<ScanResult> scanSubScription;
  BluetoothDevice beltDevice;
  BluetoothCharacteristic characteristicNotify;
  BluetoothCharacteristic characteristicWrite;
  String bleNotifyString = "";

  BleBeltNotifyParserBloc _bloc = BleBeltNotifyParserBloc();

  BehaviorSubject<String> _bleStatusFromBeltController = BehaviorSubject<String>.seeded("BLE STATUS");
  Stream<String> get bleStatusFromBelt => _bleStatusFromBeltController.stream;

  StreamController<String> _bleNotifyFromBeltController = BehaviorSubject<String>.seeded("BLE NOTIFY");
  Stream<String> get bleNotifyFromBelt => _bleNotifyFromBeltController.stream;
  Sink<String> get bleNotifyFromBeltSink => _bleNotifyFromBeltController.sink;

  BleService();

  dispose() {
    _bleStatusFromBeltController.close();
    _bleNotifyFromBeltController.close();
  }

  startScan() {
    //stopScan();
//    // SCANNING
    scanSubScription = flutterBlue.scan().listen((scanResult) async {
      if (scanResult.device.name == TARGET_DEVICE_NAME) {
        stopScan();
//        // FOUND
        beltDevice = await Future.value(scanResult.device).timeout(const Duration(seconds: 3));
        connectToDevice();
      }
    }, onDone: () => stopScan());
  }

  stopScan() {
    flutterBlue.stopScan();
    scanSubScription?.cancel();
    scanSubScription = null;
    _bleStatusFromBeltController.add("Disconnected");
    print("print Disconnected");
  }

  connectToDevice() async {
    if (beltDevice == null) return;
    // CONNECTING
    await beltDevice.connect();
    beltDevice.requestMtu(185);
    print('print DEVICE CONNECTED');
    print(" print BeltDevice $beltDevice");
    _bleStatusFromBeltController.add("Connected");
    print("print Connected");
    //discoverServices();
  }

  discoverServices() async {
    print("discoverServices beltDevice name is  $beltDevice");
    if (beltDevice == null) return;
    List<BluetoothService> services = await beltDevice.discoverServices();
    services.forEach((service) {
      // do something with service
      if (service.uuid.toString() == SERVICE_UUID) {
        service.characteristics.forEach((characteristic) {
          // set up notify characteristic
          print("Service Found for $characteristic");
          if (characteristic.uuid.toString() == NOTIFY_UUID) {
            characteristicNotify = characteristic;
            // tell characteristic on server to notify
            characteristicNotify.setNotifyValue(true);
            print("notify set to true");
            // listen, convert and put notify value in stream
            characteristicNotify.value.listen((value) {
              bleNotifyString = utf8.decode(value);
              //print("got characteristic $value");
              print(bleNotifyString);
              _bleNotifyFromBeltController.sink.add(bleNotifyString);
              _bloc.parserInputSink.add(bleNotifyString);
            });
            // COMMUNICATING
          }
          // Prepares characteristic for Write
          if (characteristic.uuid.toString() == WRITE_UUID) {
            characteristicWrite = characteristic;
          }
        });
      }
    });
  }
  disconnectFromDevice() {
    //if (beltDevice == null) return;
    //stopScan();
    beltDevice.disconnect();
    _bleStatusFromBeltController.add("Disconnect");
    print("Disconnect");
    // DISCONNECTED
  }
}

My bleBeltNotifyParser_bloc I can not render

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:math' as Math;
import 'package:flutterappbelt3/blocs/ble_service.dart';


class BleBeltNotifyParserBloc{

 // final BleService _bloc = BleService();

  StreamController<String> _parserInputController = BehaviorSubject<String>.seeded("Parser Input");
  Stream<String> get parserInput => _parserInputController.stream;
  Sink<String> get parserInputSink => _parserInputController.sink;

  BleBeltNotifyParserBloc(){


    _parserInputController.stream.listen(_aTest2);

    //_bloc.bleNotifyFromBelt.listen((String data) {parserInputSink.add(data); print('Got eem! Input Parser $data');}); Tried various things - such as listening to streams originating from ble_service.


  }

  void _aTest2(data) {
    print("WHAT!!! $data");
  }

  void aTest() {
    //_bloc.bleNotifyFromBelt.listen((String data) {_parserInputController.sink.add(data);});
  }

  void addToParserController1() {
    _parserInputController.sink.add("One");
  }

  void addToParserController2() {
    _parserInputController.sink.add("Two");
  }

  dispose() {
    _parserInputController.close();
  }
}


Solution

  • I didn't want to leave this question unanswered so I wanted to point out the answer to this question lies in the answer to this other question I posted.

    Why won't any changes from NotifyParser render in the UI using Provider / ChangeNotifier / Streambuilder but will from a Service Class

    A comment from that answered question that refers to this question.

    I read it but I don't fully understand the problem (you made a lot of changes to test the stream that I don't know what was the real issue without the tests) but I saw you did the same thing by creating a BleBeltNotifyParserBloc called _bloc inside BleService, and I believe that was the reason it didn't update the UI (for the same reason it didn't work here).

    In this example I was trying an Inherited Widget / Bloc architecture with streams and then moved in the other question to a Provider / Bloc / Async model to try and figure out why the UI was not updating when sending data from the BleService bloc to the NotifyParser Bloc.

    I need to thank https://stackoverflow.com/users/3547212/edwynzn for his answer on the linked question!!