Search code examples
flutterflutter-provider

Provider notify other listeners from a ChangeNotifier


I have the following setup:

App with multiple pages: VoltagesPage & TemperaturesPage

I will receive some data over bluetooth serial like so:

bluetooth_data_provider.dart

import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/widgets.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';

class BluetoothDataProvider with ChangeNotifier {
  String _data = '';
  String get data => _data;
  String _messageBuffer = '';

  BluetoothConnection? _connection;

  connectAndListen(String address) {
    BluetoothConnection.toAddress(address).then((connection) {
      print('Connected to the device');
      _connection = connection;

      if (_connection == null) {
        return;
      }

      _connection!.input!.listen(_onDataReceived).onDone(() {});
    }).catchError((error) {
      print('Cannot connect, exception occured');
      print(error);
    });
  }

  void _onDataReceived(Uint8List data) {
    String dataStr = ascii.decode(data);
    _messageBuffer += dataStr;

    if (dataStr.contains('\n')) {
      print(_messageBuffer);
      _data = _messageBuffer.substring(0, _messageBuffer.length - 1);
      _messageBuffer = '';

      notifyListeners();
    }
  }
}

This will notify all listeners, that listen to BluetoothDataProvider's changes to its data field.

Now the TemperaturesPage is not really interested in a message, that is meant for the VoltagesPage and vice versa. I can identify who should receive the message by a prefix that is being sent by the device.

A voltage message can look like this:

V:+12.5

A temperature message can look like this:

T:+45.6

Right now when I'm watching the BluetoothDataProvider, from both of the pages, the widget has to decide whether to accept the message, or not. But this might leave a widget hanging, because it still needs to rebuild the widget as build is being called.

What I really want is something like this:

import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/widgets.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';

class BluetoothDataProvider with ChangeNotifier {
  String _data = '';
  String get data => _data;
  String _messageBuffer = '';

  BluetoothConnection? _connection;

  connectAndListen(String address) {
    BluetoothConnection.toAddress(address).then((connection) {
      print('Connected to the device');
      _connection = connection;

      if (_connection == null) {
        return;
      }

      _connection!.input!.listen(_onDataReceived).onDone(() {});
    }).catchError((error) {
      print('Cannot connect, exception occured');
      print(error);
    });
  }

  void _onDataReceived(Uint8List data) {
    String dataStr = ascii.decode(data);
    _messageBuffer += dataStr;

    if (dataStr.contains('\n')) {
      print(_messageBuffer);
      _data = _messageBuffer.substring(0, _messageBuffer.length - 1);
      _messageBuffer = ''; 

      if(_data.startsWith("T:")) {
        // notify with a TemperatureProvider
      } else if (_data.startsWith("V:")){
        // notify with a VoltageProvider
      }
    }
  }
}

That way, each page could listen to a different Provider and only receive data they are actually interested in.

Is a scenario like this possible?

Thanks!


Solution

  • I solved this now.

    In my main.dart, I will just create an instance of BluetoothDataProvider in initState() and providing the context from there to the constructor of BluetoothDataProvider.

    After that, I am free to use the context to call different providers as follows:

    void _onDataReceived(Uint8List data) {
      String dataStr = ascii.decode(data);
      _messageBuffer += dataStr;
      if (dataStr.contains('\n')) {
        print(_messageBuffer); // here you get complete string
        _data = _messageBuffer.substring(0, _messageBuffer.length - 1);
        _messageBuffer = ''; //clear buffer to accept new string
        var messageType = _data.substring(0, 2);
    
        switch (messageType) {
          case "V:":
            context.read<CurrentReceivedMessage>().setMessage(_data);
            break;
          case "T:":
            context.read<TemperatureReceivedMessage>().setMessage(_data);
            break;
        }
      }
    }