为啥 NotifyParser 的任何更改都不会使用 Provider / ChangeNotifier / Streambuilder 在 UI 中呈现,而是来自服务类

Posted

技术标签:

【中文标题】为啥 NotifyParser 的任何更改都不会使用 Provider / ChangeNotifier / Streambuilder 在 UI 中呈现,而是来自服务类【英文标题】:Why won't any changes from NotifyParser render in the UI using Provider / ChangeNotifier / Streambuilder but will from a Service Class为什么 NotifyParser 的任何更改都不会使用 Provider / ChangeNotifier / Streambuilder 在 UI 中呈现,而是来自服务类 【发布时间】:2020-10-25 08:47:32 【问题描述】:

根据示例代码,来自 Ble_Service 的任何数据都可以通过提供程序在 UI 中呈现更改。但是,如果我通过 changeParserInput() 将任何数据从 Ble_Service 传递给 NotifyParser,json 字符串就会结束,但无论我遵循哪种模式,我都无法让 UI 识别任何状态已更改并更新超出初始种子的 UI价值。

Ble_Service -> UI //没问题 Ble_Service -> NotifyParserBloc -> UI // 数据不成功。

为什么? (被注释掉的代码来自尝试过的许多不同的变体)


import 'package:flutter/material.dart';
import 'package:flutter_app_belt_provider/blocs/notifyParser_bloc.dart';
import 'package:flutter_app_belt_provider/services/bleBelt_service.dart';
import 'package:flutter_app_belt_provider/services/service_locator.dart';
import 'package:provider/provider.dart';

void main() 
  setupServiceLocator(); // <-- get it service locator function
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => BleService()),
        ChangeNotifierProvider(create: (context) => NotifyParserBloc()),
        //StreamProvider.value(value: NotifyParserBloc().parserInput)
      ],
      child: MyApp(),
    ),
  );


class MyApp extends StatelessWidget 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  


class MyHomePage extends StatefulWidget 

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 

  //final NotifyParserBloc _notify = NotifyParserBloc();

  @override
  Widget build(BuildContext context) 
    var _service =  Provider.of<BleService>(context);
    //var _bloc = Provider.of<NotifyParserBloc>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        title: Text("poo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              child: FlatButton(
                child: Text(
                  "Scan",
                ),
                onPressed: () 
                 _service.startScan();
                ,
              )
            ),
            Container(
                child: FlatButton(
                  child: Text(
                    "Connect",
                  ),
                  onPressed: () 
                    _service.connectToDevice();
                  ,
                )
            ),
            Container(
                child: FlatButton(
                  child: Text(
                    "Services",
                  ),
                  onPressed: () 
                    _service.discoverServices();
                  ,
                )
            ),
            Container(
                child: FlatButton(
                  child: Text(
                    "Disconnect",
                  ),
                  onPressed: () 
                    _service.disconnectFromDevice();
                  ,
                )
            ),
            Consumer<NotifyParserBloc>(
              builder: (context, notifyParserBloc, child) => Text(notifyParserBloc.parserInput)
            ),
            Consumer<BleService>(
                builder: (context, bleService, child) => Text(bleService.viaService)
            ),
            StreamBuilder<String>(
                initialData: "0",
                stream: Provider.of<NotifyParserBloc>(context, listen: false).parserInputStream,
                builder: (context, snapshot) 
                  if(snapshot.data == null) return CircularProgressIndicator();
                  else return Text(
                    snapshot.data.toString(),
                    style: TextStyle(fontSize: 14.0, color: Colors.black),
                    textAlign: TextAlign.center,
                  );
            ),
//              StreamProvider<String>.value(
//               initialData: "go",
//               value: _bloc.parserInput,
//               child: ValueWidget(),
//           ),
          ],
        ),
      ),
    );
  


class ValueWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    //var model = Provider.of<NotifyParserBloc>(context);
    return Column(
      children: <Widget>[
        Text('Listening a value :' + Provider.of<String>(context).toString(),),
      ],
    );
  


ble_service


import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter_app_belt_provider/services/service_locator.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'dart:convert' show utf8;
import 'package:rxdart/rxdart.dart';
import 'package:flutter_app_belt_provider/blocs/notifyParser_bloc.dart';



class BleService with ChangeNotifier 


//NotifyParserBloc _bloc = NotifyParserBloc();
var notifyParser = sl<NotifyParserBloc>();

//class BleService

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

  BehaviorSubject<String> _bleButtonTextController = BehaviorSubject<String>.seeded("button text");
  Stream<String> get bleButtonTextGet => _bleButtonTextController.stream;

  //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 = scanResult.device;
        _bleStatusFromBeltController.add("Found");
        //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);
              notifyParser.changeParserInput(bleNotifyString);
              print("print Transmitting");
              viaServiceChangeParserInput(bleNotifyString);
              //_bleNotifyFromBeltController.sink.add(bleNotifyString);
              //_bloc.parserInputSink.add(bleNotifyString);
              //_bleStatusFromBeltController.add("Transmitting");

            );
            // COMMUNICATING
          
          // Prepares characteristic for Write
          if (characteristic.uuid.toString() == WRITE_UUID) 
            characteristicWrite = characteristic;
          
        );
      
    );
  

  String _viaServiceChangeParserInput = "via Service";
  String get viaService => _viaServiceChangeParserInput;


viaServiceChangeParserInput(String value) 
    _viaServiceChangeParserInput = value;
    print("via Service $_viaServiceChangeParserInput");
    notifyListeners();


  disconnectFromDevice() 
    beltDevice.disconnect();
    _bleStatusFromBeltController.add("Disconnected");
    print("Disconnected");
    // DISCONNECTED
  

  bleButtonText() async 
    _bleStatusFromBeltController.listen((String data) 
      String buttonText;
      if (data == "Disconnected") buttonText = "SCAN";
      else if (data == "Found") buttonText = "CONNECT";
      else if (data == "Connected") buttonText = "DISCONNECT";
      return buttonText;
    );
  

通知解析器

import 'package:flutter/foundation.dart';
import 'package:flutter_app_belt_provider/services/bleBelt_service.dart';
import 'dart:async';
import 'package:rxdart/rxdart.dart';

class NotifyParserBloc with ChangeNotifier
//  NotifyParserBloc(@required this.bleService);
//  final BleService bleService;
//final BleService bleService;

StreamController<String> _notifyParserController = BehaviorSubject<String>.seeded("parseNotify");
Stream<String> get parserInputStream => _notifyParserController.stream;
//
//  Future<void> parseNotify(String data) async 
//    _notifyParserController.add(data);
//    print("PARSENOTIFYBLOC: $data");
//    _notifyParserController.stream.listen((event) 
//      print("NOTIFYPARSERSTREAM: $event");
//    );
//  

  String _parserInput;

  NotifyParserBloc()
    _parserInput = "let's get this started";
  

  String get parserInput => _parserInput;

  void changeParserInput(String value) 
     _parserInput = value;
     notifyListeners();
     print("change parser value $_parserInput");
     _notifyParserController.add(value);
     _notifyParserController.stream.listen((event) 
      print("NOTIFYPARSERSTREAM: $event");
     );
  

【问题讨论】:

【参考方案1】:

我不知道sl&lt;NotifyParserBloc&gt;() 是什么,但 BleService 中的 var notifyParser 它与您在 Provider 中创建的实例不同(因此更新/通知对它的更改实际上不会通知取决于用户界面)。如果您想在其他提供者中保存一个实例,可以尝试这样的操作

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (context) => NotifyParserBloc()),
    ChangeNotifierProvider(create: (context) => BleService(Provider.of<NotifyParserBloc>(context, listen: false)),
  ],
  child: MyApp(),
),

class BleService with ChangeNotifier 
   final notifyParser;

   BleService(this.notifyParser); //save the instance in the constructor

   ...
   notifyParser.changeParserInput(bleNotifyString); //now it will update the one using the UI

更新

您可以尝试同样的方法传递定位器而不是 Provider.of&lt;NotifyParserBloc&gt;(context, listen: false) 以提高可读性

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (context) => NotifyParserBloc()),
    ChangeNotifierProvider(create: (context) => BleService(context.read)), //this is an extension method that allows us to use the Locator
  ],
  child: MyApp(),
),

class BleService with ChangeNotifier 
   final notifyParser;

   BleService(Locator locator) : notifyParser = locator<NotifyParserBloc>(); //save the instance in the constructor

   ...
   notifyParser.changeParserInput(bleNotifyString); //now it will update the one using the UI

使用 GET 更新

如果您已经在 getit (sl()) 中有一个实例并且想要将其公开给提供者,只需将您的 ChangeNotifierProvider 更改为 .value

MultiProvider(
  providers: [
    ChangeNotifierProvider.value(value: sl<NotifyParserBloc>()), //this will expose the same instance of the get it to the UI
    ChangeNotifierProvider(create: (context) => BleService())
  ],
  child: MyApp(),
),

class BleService with ChangeNotifier 
   var notifyParser = sl<NotifyParserBloc>();
   // No need for the constructor now

   ...
   notifyParser.changeParserInput(bleNotifyString); //now it will update the one using the UI

这样,get_it 中对实例的更改将从 Provider 通知 UI

【讨论】:

感谢您的帮助。 sl() 是刚刚从 NotifyParserBloc _bloc = NotifyParserBloc() 更改的 getit 调用。有了它,我可以调用 _bloc.changeParserInput(bleNotifyString) 将该 json 字符串传递给 NotifyParser,但随后不会进入 UI。 我看了你的建议。添加 ChangeNotifierProvider(create: (context) => BleService(Provider.of(context), listen: false)),在监听时出现 IDE 错误,提示未定义命名参数。然后它建议我将 bool listen 添加到您建议的行中。 BleService(this.notifyParser, bool listen);通过此更改,以前在主页上引用 BleService 的任何内容都不起作用。例如 - 调用 Ble 服务的 FlatButtons 都不执行 - 该方法在 null 上调用。 我不知道该问谷歌什么来帮助我理解这两个类之间的这种关系以及如何将数据从一个到另一个传递到 UI 上。例如 bleService 管理 ble 并将其内容传递给解析有效负载并将其发送到 UI 的通知解析器。我有几个概念证明,所有东西都在一个类中。但是一旦我打破它 - 我遇到了这个我无法看到的障碍。 对不起,这是我的拼写错误,我在listen之前关闭了括号(listen是Provider中of方法的命名参数),我编辑了我的代码,再试一次 ChangeNotifierProvider(create: (context) => BleService(Provider.of(context, listen: false)) 它有效。谢谢你。松了一口气。这里发生了什么事?您在 BleService 构造函数 (this.notifyParser) 中定义“notifyParser”,然后使用它来定位和调用 NotifyParserBelt 类中的函数 changeParserInput()。我是否正确地说数据流过,但问题是 UI 不知道如何更新?通过在提供者中遵循您的建议 - BleService(Provider.of(context, listen: false) 告诉 UI 在从 BleService 调用到 NotifyParser 时进行更新?

以上是关于为啥 NotifyParser 的任何更改都不会使用 Provider / ChangeNotifier / Streambuilder 在 UI 中呈现,而是来自服务类的主要内容,如果未能解决你的问题,请参考以下文章

为啥 jQuery.val(value) 不会从 DOM 元素中分派任何事件?

释放自动释放对象不会使我的应用程序崩溃,为啥?

为啥核心数据不会持久化到磁盘?

为啥不传递更新的元素值?

为啥 .geocodeAddressString 不会更改外部变量?

指针向量:为啥在外部更改指针不会更改向量元素?