flutter) Bluetooth provider error =>setState() or markNeedsBuild() called during build

454

You are actually encountering this error because you try to rebuild the widget tree while it's being build.

Your call on _bluetoothProvider.startScan();in your Consumer's builder method will call the notifyListeners method which actually tries to rebuild the tree while it's being build, thus that exception will be thrown.

WHY? The Consumer widget is actually listening to changes on your BluetoothProvider; so when you call the notifyListeners on the BluetothProvider class, the Consumer tries to rebuild itself, which is not authorized.

A solution would be to first build the tree, and then call the startScan method.

You could try this:

Provider Code

class BluetoothProvider with ChangeNotifier{
   final String SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
   final String CHARACTERISTIC_UUID="0000ffe1-0000-1000-8000-00805f9b34fb";
   final String TARGET_DEVICE_NAME="HMSoft";

   FlutterBlue flutterBlue = FlutterBlue.instance;
   StreamSubscription<ScanResult> scanSubScription;

   BluetoothDevice targetDevice;
   BluetoothCharacteristic targetCharacteristic;
   BluetoothState bluetoothState;

   String connectionText="";
   String joystick="";

   startScan() {
      connectionText="Start Scanning";

      scanSubScription = flutterBlue.scan().listen((scanResult){
         if(scanResult.device.name==TARGET_DEVICE_NAME){
            print("Device Found");
         
            stopScan();
            connectionText="Found Target Device";
            targetDevice = scanResult.device;
         }
      }, onDone: () => stopScan());
      notifyListeners();
   }

   stopScan() {
      scanSubScription?.cancel();
      scanSubScription=null;
      notifyListeners();
   }

   connectToDevice() async{
      if(targetDevice==null) return;

      connectionText = "Device Connecting";

      await targetDevice.connect();
      print("Device Connected");

      connectionText="Device Connected";
      discoverServices();
      notifyListeners();
   }

   disconnectFromDevice(){
      if(targetDevice==null) return;
      targetDevice.disconnect();

      connectionText="Device Disconnected";
      notifyListeners();
   }

   discoverServices() async {
       if(targetDevice==null) return;

       List<BluetoothService> services = await targetDevice.discoverServices();
       services.forEach((service) {

          if(service.uuid.toString() == SERVICE_UUID){
             service.characteristics.forEach((characteristc) {
                if (characteristc.uuid.toString() == CHARACTERISTIC_UUID) {
                   targetCharacteristic = characteristc;
                   writeData("Connect Complete!\r\n");

                   connectionText = "All Ready with ${targetDevice.name}";
                }
             });
          }
       });
       notifyListeners();
   }

   writeData(String data) async{
      if(targetCharacteristic==null) return;
     
      List<int> bytes = utf8.encode(data);
      await targetCharacteristic.write(bytes);
      notifyListeners();
  }
}

Widget code

class JoyPad extends StatefulWidget {
   @override
   _JoyPadState createState() => _JoyPadState();
}

class _JoyPadState extends State<JoyPad> {

  @override
  void initState() {
     super.initState();
     WidgetsBinding.instance.addPostFrameCallback((_) {
        // The code in this block will be executed after the build method
        context.read<BluetoothProvider>().startScan();
     });
  }

  @override
  Widget build(BuildContext context) {
     return Consumer<BluetoothProvider>(
        builder:(context,provider,child) {
           return Scaffold(
              appBar: AppBar(
                 title: Text(_bluetoothProvider.connectionText),
                 backgroundColor: Colors.indigoAccent,
                 actions: <Widget>[
                    IconButton(
                       icon: Icon(Icons.bluetooth), iconSize: 30,
                       onPressed: () {
                           _bluetoothProvider.connectToDevice();
                           print(_bluetoothProvider.bluetoothState.toString());
                       },
                    ),
                    IconButton(
                       icon: Icon(Icons.bluetooth_disabled), iconSize: 30,
                       onPressed: () {
                          _bluetoothProvider.disconnectFromDevice();
                          print(_bluetoothProvider.bluetoothState.toString());
                       },
                    ),
                 ],
              ),
              body: joystickWidget(),
           );
        });
     }
  }
}

context.read<BluetoothProvider>().startScan(); is a shortcut for Provider.of<BluetoothProvider>(context, listen: false).startScan() : it basically does the same thing.

Share:
454
youna
Author by

youna

Updated on December 28, 2022

Comments

  • youna
    youna over 1 year

    I'm trying to develop "BLE Control App" with using flutter_Blue. I added a tab bar so I want to Maintain Bluetooth State "Connect". so I'm trying to use Provider, To set connection state but I have an error like this.

    **======== Exception caught by foundation library ====================================================
    The following assertion was thrown while dispatching notifications for BluetoothProvider:
    setState() or markNeedsBuild() called during build.
    This _InheritedProviderScope<BluetoothProvider> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
    The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<BluetoothProvider>
      value: Instance of 'BluetoothProvider'
      listening to value
    The widget which was currently being built when the offending call was made was: Consumer<BluetoothProvider>
      dirty
      dependencies: [_InheritedProviderScope<BluetoothProvider>]
    When the exception was thrown, this was the stack: 
    #0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4138:11)
    #1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4153:6)
    #2      _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:531:5)
    #3      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:243:25)
    #4      BluetoothProvider.startScan (package:flutter_joystick/provider/bluetooth_provider.dart:46:5)
    ...
    The BluetoothProvider sending notification was: Instance of 'BluetoothProvider'**
    

    this is my bluetooth provider code

    class BluetoothProvider with ChangeNotifier{
     final String SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
     final String CHARACTERISTIC_UUID="0000ffe1-0000-1000-8000-00805f9b34fb";
     final String TARGET_DEVICE_NAME="HMSoft";
    
     FlutterBlue flutterBlue = FlutterBlue.instance;
     StreamSubscription<ScanResult> scanSubScription;
    
     BluetoothDevice targetDevice;
     BluetoothCharacteristic targetCharacteristic;
     BluetoothState bluetoothState;
    
     String connectionText="";
     String joystick="";
    
     startScan(){
    
      connectionText="Start Scanning";
    
      scanSubScription = flutterBlue.scan().listen((scanResult){
      if(scanResult.device.name==TARGET_DEVICE_NAME){
        print("Device Found");
       
        stopScan();
    
     
         connectionText="Found Target Device";
    
        targetDevice = scanResult.device;
         }
       }, onDone: () => stopScan());
        notifyListeners();
       }
     stopScan(){
       scanSubScription?.cancel();
       scanSubScription=null;
       notifyListeners();
     }
    
     connectToDevice() async{
       if(targetDevice==null) return;
    
      connectionText = "Device Connecting";
    
      await targetDevice.connect();
      print("Device Connected");
    
      connectionText="Device Connected";
    
    
       discoverServices();
       notifyListeners();
     }
    
     disconnectFromDevice(){
       if(targetDevice==null) return;
       targetDevice.disconnect();
    
      connectionText="Device Disconnected";
    
       notifyListeners();
     }
    
     discoverServices() async{
    
       if(targetDevice==null) return;
    
       List<BluetoothService> services = await targetDevice.discoverServices();
       services.forEach((service) {
    
         if(service.uuid.toString() == SERVICE_UUID){
          service.characteristics.forEach((characteristc) {
          if (characteristc.uuid.toString() == CHARACTERISTIC_UUID) {
            targetCharacteristic = characteristc;
            writeData("Connect Complete!\r\n");
    
              connectionText = "All Ready with ${targetDevice.name}";
    
            }
    
          });
        }
    
        }
       );
       notifyListeners();
      }
      writeData(String data) async{
      if(targetCharacteristic==null) return;
      List<int> bytes = utf8.encode(data);
      await targetCharacteristic.write(bytes);
       notifyListeners();
      }
     }
    

    Funny, the Bluetooth connection is progressing, but the error written above keeps coming up through the console window.

    The first page of the Tab Bar is the joystick page, and Bluetooth is connected due to an error, but the joystick is not working.

    Here is Joystick code

    class JoyPad extends StatefulWidget {
     @override
     _JoyPadState createState() => _JoyPadState();
    }
    
    class _JoyPadState extends State<JoyPad> {
    BluetoothProvider _bluetoothProvider;
    
     @override
     Widget build(BuildContext context) {
     _bluetoothProvider = Provider.of<BluetoothProvider>(context,listen:false);
        return Consumer<BluetoothProvider>(
             builder:(context,provider,child) {
              _bluetoothProvider.startScan();
              return Scaffold(
                appBar: AppBar(
                  title: Text(_bluetoothProvider.connectionText),
                  backgroundColor: Colors.indigoAccent,
                  actions: <Widget>[
                    IconButton(
                      icon: Icon(Icons.bluetooth), iconSize: 30,
                      onPressed: () {
                        _bluetoothProvider.connectToDevice();
                        print(_bluetoothProvider.bluetoothState.toString());
                      },
                    ),
                    IconButton(
                        icon: Icon(Icons.bluetooth_disabled), iconSize: 30,
                        onPressed: () {
                          _bluetoothProvider.disconnectFromDevice();
                          print(_bluetoothProvider.bluetoothState.toString());
                        }),
                  ],
                ),
                  body: joystickWidget(),
                 );
                });
    
             }
           }
    

    Additionally, the provider does not "setState" so I try to display connection text according to the status change on the App Bar, but it is not possible. I would also appreciate it if you could tell me how to solve it.