Flutter Bloc A 在 Bloc B 中添加流,但此流不会通过 StreamBuilder 在 UI 中呈现

Posted

技术标签:

【中文标题】Flutter Bloc A 在 Bloc B 中添加流,但此流不会通过 StreamBuilder 在 UI 中呈现【英文标题】:Flutter Bloc A adds stream in Bloc B but this stream will not render in UI via StreamBuilder 【发布时间】:2020-10-19 03:30:39 【问题描述】:

我正在尝试为一个新的 Flutter 项目转换为 bloc/provider 架构。一段时间以来,我一直在解决一个问题,其中一个块的流将通过 StreamBuilder 在 UI 上呈现,而另一个块则不会。在我继续之前,我很想(并且需要)了解原因。

我有两个 bloc,第一个是 ble_service。我可以从 UI 调用函数到这个 bloc(app.dart 第 60-72 行)连接到设备并通过 StreamBuilder(第 98 行)呈现特征在 UI 中返回的内容。只需在 UI 中渲染从 ble 设备返回的 json 有效负载。这会非常频繁地每秒更新多次。

我的计划是有一个解析器块 (bleBeltNotifyParser_bloc) 来解析来自 ble_service 的传入 json 有效负载,然后从那里的 UI 流式传输。在 ble_service 中,我将 json 有效负载传递给 parserInputSink,这是来自 Parser Bloc 的一个流(ble_service 第 99 行)。在 bleBeltNotifyParser.bloc 中,我在第 21 行监听它并将它传递给我计划解析它的 aTest2()。我在这里停了下来,因为我试图在 UI 上呈现这些数据(app.dart 第 108 行),但是无论将数据传递到 parserInputController 的不同组合如何,UI 都只会呈现种子数据。我通过在第 28 行打印数据来确认流正在获取数据。

我还确认我可以通过按钮(第 73 行和第 80 行)将一些数据放入流中,从而从 UI 访问 Parser 块。当按下这些按钮时,该数据将添加到流中,并且 UI 会按预期更新。

为什么 StreamBuilder 适用于 blue_service 而不适用于 bleBeltNotifyParser_bloc?我还尝试在服务中创建流,然后在解析器中收听它。他们也没有运气。

我的 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(),
    ),
  );

我的应用状态

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,

  );

我的 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,
                    );
              ),
            ),
          ],
        ),
      ),
    );
  

我的 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
  

我的 bleBeltNotifyParser_bloc 我无法渲染

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();
  


【问题讨论】:

【参考方案1】:

我不想让这个问题悬而未决,所以我想指出这个问题的答案在于我发布的另一个问题的答案。

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

来自已回答问题的与该问题相关的评论。

我读过它,但我不完全理解问题(你做了很多改变来测试流,如果没有测试,我不知道真正的问题是什么)但我看到你做了同样的事情在 BleService 中创建一个名为 _bloc 的 BleBeltNotifyParserBloc,我相信这就是它没有更新 UI 的原因(出于同样的原因,它在这里不起作用)。

在这个例子中,我尝试了一个带有流的 Inherited Widget / Bloc 架构,然后将另一个问题移至 Provider / Bloc / Async 模型,以尝试找出从 BleService bloc 发送数据时 UI 没有更新的原因到 NotifyParser Bloc。

我要感谢 https://***.com/users/3547212/edwynzn 他对链接问题的回答!!

【讨论】:

以上是关于Flutter Bloc A 在 Bloc B 中添加流,但此流不会通过 StreamBuilder 在 UI 中呈现的主要内容,如果未能解决你的问题,请参考以下文章

flutter - bloc - 我如何在我的 Ui 中使用 FutureBuilder 来正确实现 Bloc 架构

Flutter Bloc Test:空值检查运算符用于空值(空安全)

谁能说出 Flutter 中“flutter_bloc”和“bloc”包的区别

Bloc 模式是不是适合在 Flutter 应用中管理导航?

Flutter:BLoC 包 - bloc 提供者

Flutter BloC 模式:基于另一个 BloC 的流更新 BloC 流