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

Issue

Per the example code, any data coming from Ble_Service has no problems rendering changes in the UI via provider. But if I pass any of that data from Ble_Service to NotifyParser via changeParserInput() the json string makes it over but no matter the patter I follow I can’t get the UI to recognize any state had changed and update the UI beyond the initial seed value.

Ble_Service -> UI //no problem
Ble_Service -> NotifyParserBloc -> UI // Data doesn’t make it.

Why?
(the code that is commented out is from the many different variations tried)


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

Notify Parser

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");
     });
  }
}

Solution

I don’t know what is sl<NotifyParserBloc>() but the var notifyParser inside the BleService it’s not the same instance as the one you created in the Provider (so updating/notifying changes to it doesn’t actually notify the one depending in the UI). If you want to save an instance in the other provider maybe try something like this

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
}

UPDATE

You can try the same passing a Locator instead of the Provider.of<NotifyParserBloc>(context, listen: false) for readability

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
}

UPDATE USING GET IT

If you already had an instance in the getit (sl()) and want to expose it to the provider just change your ChangeNotifierProvider to .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
}

This way the changes to the instance in the get_it will notifiy the UI from the Provider

Answered By – EdwynZN

Answer Checked By – Senaida (FlutterFixes Volunteer)

Leave a Reply

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