Stream Listening For List Updates - flutter

I'm trying to understand how to use streams to notify listeners when there is a new addition to a list, below I wrote an example program that sums up what I'm trying to accomplish. Everytime there is a NEW string added to the existing 'names' list, I would like the stream to notify its listeners.
import 'dart:async';
import 'dart:io';
import 'dart:math';
List<String> names = ['matt', 'luke', 'sam'];
// pushes random string onto 'names' list every 3 seconds
void addNamesToNameList() async {
Random random = Random();
int len = 4;
while(true) {
String new_name = String.fromCharCodes(List.generate(len, (index) => random.nextInt(33) + 89));
names.add(new_name);
print("Added $new_name to names list");
sleep(Duration(seconds: 3));
}
}
void main() {
addNamesToNameList();
/*
Insert stream that is notified every time there is a NEW addition to the 'names' list
*/
}

You can use Stream.periodic to accomplish this.
late final List<String> names = [];
late final Stream<String> stream = randomStream().asBroadcastStream();
//use [asBroadcastStream] if you have multiple listeners.
String generateNewName(int i) {
Random random = Random();
int len = 4;
return String.fromCharCodes(
List.generate(len, (index) => random.nextInt(33) + 89));
}
Stream<String> randomStream() async* {
yield* Stream.periodic(
const Duration(seconds: 3),
generateNewName,
).asyncMap((event) => event);
}
and then listen to changes:
stream.listen((event) {
setState(() => names.add(event));
});

Related

Stream created using StreamController and await for

I am learning stream in dart.
The following code shows a generator method(countStream) and a method that uses the stream(sumStream), they are from sample code from dart.dev. It works, meaning that
I can see the following output at the end.
end of for
total = 15
However, when I try to create a stream using makeCounter where I use StreamController to create a stream instead of generator (async* and yield), I can see the following output.
add
add
add
add
add
I suppose that makeCounter works because I see five "add".
How to fix this problem? Or It may impossible to create a stream with StreamController with await for.
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (final value in stream) {
sum += value;
}
print("end of for");
return sum;
}
Stream<int> makeCounter(int to) {
var controller = StreamController<int>();
int counter = 0;
void tick(Timer timer) {
counter++;
print("add");
controller.add(counter);
if (counter >= to) {
timer.cancel();
}
}
Timer.periodic(Duration(seconds: 1), tick);
return controller.stream;
}
Stream<int> countStream(int to) async* {
for (int i = 1; i <= to; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void test() async {
var stream = countStream(5);
//var stream = makeCounter(5); // this does not work correctly
var sum = await sumStream(stream);
print("total = $sum");
}

How to retrieve the value of a listener passed to a Stream?

Stream<int> get ints async* {
yield 100;
}
Future<int> foo(int i) async {
print(i);
return 0;
}
void main() {
final subscription = ints.listen(foo);
// I thought something like this could work...
subscription.asFuture().then((value) => print('value = $value'));
}
How can I retrieve the value of foo function (i.e. 0) from the StreamSubscription instance?

rx dart combine multiple streams to emit value whenever any of the streams emit a value

In RX dart there is the RX.combineLatest method to combine the results of a stream using a callback function.
Problem is that it only emits a value when every stream has emitted a value. If one has not it does not emit.
Merges the given Streams into a single Stream sequence by using the combiner function whenever any of the stream sequences emits an item.
The Stream will not emit until all streams have emitted at least one item.
Im trying to combine multiple streams into one stream for validation which should emit false or true when the streams have not emitted or emitted an empty value.
class FormBloc {
final BehaviorSubject<bool> _result = BehaviorSubject();
final BehaviorSubject<String?> _usernameController = BehaviorSubject();
final BehaviorSubject<String?> _emailController = BehaviorSubject();
// Will only emit if each stream emitted a value
// If only username is emitted valid is not emitted
Stream<bool> get valid$ => Rx.combineLatest2(
_usernameController.stream,
_emailController.stream,
(username, email) => username != null || email != null
);
}
How can I join those streams so valid$ emits a value if any of the streams change?
Because all of the solutions here are workarounds Ive implemented my own stream class. Implementation equals the original CombineLatestStream implementation except that it does not wait for all streams to emit before emitting:
import 'dart:async';
import 'package:rxdart/src/utils/collection_extensions.dart';
import 'package:rxdart/src/utils/subscription.dart';
class CombineAnyLatestStream<T, R> extends StreamView<R> {
CombineAnyLatestStream(List<Stream<T>> streams, R Function(List<T?>) combiner) : super(_buildController(streams, combiner).stream);
static StreamController<R> _buildController<T, R>(
Iterable<Stream<T>> streams,
R Function(List<T?> values) combiner,
) {
int completed = 0;
late List<StreamSubscription<T>> subscriptions;
List<T?>? values;
final _controller = StreamController<R>(sync: true);
_controller.onListen = () {
void onDone() {
if (++completed == streams.length) {
_controller.close();
}
}
subscriptions = streams.mapIndexed((index, stream) {
return stream.listen(
(T event) {
final R combined;
if (values == null) return;
values![index] = event;
try {
combined = combiner(List<T?>.unmodifiable(values!));
} catch (e, s) {
_controller.addError(e, s);
return;
}
_controller.add(combined);
},
onError: _controller.addError,
onDone: onDone
);
}).toList(growable: false);
if (subscriptions.isEmpty) {
_controller.close();
} else {
values = List<T?>.filled(subscriptions.length, null);
}
};
_controller.onPause = () => subscriptions.pauseAll();
_controller.onResume = () => subscriptions.resumeAll();
_controller.onCancel = () {
values = null;
return subscriptions.cancelAll();
};
return _controller;
}
}
Creating new stream which emits current value and listen the stream is my best practice.
class FormBloc {
final BehaviorSubject<bool> _result = BehaviorSubject();
final BehaviorSubject<String?> _usernameController = BehaviorSubject();
final BehaviorSubject<String?> _emailController = BehaviorSubject();
final _usernameStreamController = StreamController<String?>()
..add(_usernameController.value)
..addStream(_usernameController.stream);
final _emailStreamController = StreamController<String?>()
..add(_emailController.value)
..addStream(_emailController.stream);
Stream<bool> get valid$ => Rx.combineLatest2(
_usernameStreamController.stream, // use streamController instead
_emailStreamController.stream, // use streamController instead
(username, email) => username != null || email != null
);
}
Instead of combining multiple streams, you can use one BehaviorSubject<Map<String, String?>> to emit changes in username or email.
add either changed\submitted username or email to the BehaviorSubject
_usernameEmailController.add({"uname": value},);
or
_usernameEmailController.add({"email": value},);
so that you can validate the inputs by listening to it. I used StreamBuilder to display the emitted values,
StreamBuilder<Map<String, String?>>(
stream: _usernameEmailController.stream
.map((data) {
_r = {..._r, ...data};
return _r;
}),
builder: (context, snapshot) {
return Column(
children: [
Text(snapshot.data.toString()),
if (snapshot.hasData)
Text(
"Is valid?: "
"${(snapshot.data!["uname"] != null && snapshot.data!["uname"]!.isNotEmpty) || (snapshot.data!["email"] != null && snapshot.data!["email"]!.isNotEmpty)}"
),
],
);
},
),
checkout the my solution on DartPad here.
In the DartPad I have used StreamController instead of BehaviorSubject as DartPad doesn't support rxdart Package.
But you can replace line 40 in DartPad
final StreamController<Map<String, String?>> _usernameEmailController =
StreamController();
with
final BehaviorSubject<Map<String, String?>> _usernameEmailController =
BehaviorSubject();
If you want to use BehaviorSubject.
You could seed your BehaviorSubjects with a default value of null:
final BehaviorSubject<String?> _usernameController = BehaviorSubject().seeded(null);
final BehaviorSubject<String?> _emailController = BehaviorSubject().seeded(null);
Another possibility would be to give the combined stream a seed value:
Rx.combineLatest2(
_usernameController.stream,
_emailController.stream,
(username, email) => username != null || email != null
).sharedValueSeeded(false);

Is there a way to access data coming from BLE device from single file which keeps updating?

I want to access characteristic values of BLE from one dart file, What I am doing is that I am connecting the device from one activity and then sending the device info to all other activities. But to get values I have to write the same code again and again to all activities/dart files.
For example i am connecting device in an activity like this:
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
print('DEVICE CONNECTED');
return BluetoothConnectedSuccess(device: r.device);
Here device: r.device is the device that i have connected to my Flutter App. Now if i want to display device data i have to initilaze these lines of code everytime i jump to any screen/activity:
class BluetoothConnectedSuccess extends StatefulWidget {
const BluetoothConnectedSuccess({Key key, this.device}) : super(key: key);
final BluetoothDevice device;
#override
_BluetoothConnectedSuccessState createState() =>
_BluetoothConnectedSuccessState();
}
class _BluetoothConnectedSuccessState extends State<BluetoothConnectedSuccess> {
// BLE
final String SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
final String CHARACTERISTIC_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
bool isReady;
Stream<List<int>> stream;
List<int> lastValue;
List<double> traceDust = List();
#override
void initState() {
super.initState();
isReady = false;
connectToDevice();
}
connectToDevice() async {
await widget.device.connect();
discoverServices();
}
discoverServices() async {
List<BluetoothService> services = await widget.device.discoverServices();
services.forEach((service) {
if (service.uuid.toString() == SERVICE_UUID) {
service.characteristics.forEach((characteristic) {
if (characteristic.uuid.toString() == CHARACTERISTIC_UUID) {
characteristic.setNotifyValue(!characteristic.isNotifying);
stream = characteristic.value;
print(stream);
lastValue = characteristic.lastValue;
print(lastValue);
setState(() {
isReady = true;
});
}
});
}
});
}
_dataParser(List<int> data) {
var value = Uint8List.fromList(data);
print("stream.value: $value"); // stream.value: [33]
var hr = ByteData.sublistView(value, 0, 1);
print("Heart rate: ${hr.getUint8(0)}");
return hr.getUint8(0); // Heart rate: 33
}
It's creating a lot of mess to write the same code again and again to the activities where BLE data is needed.
Is there a way to only call this connected device from a single file instead of initializing the same code in every activity?
This is the link to my repo for a look at what I am doing on every activity/screen with BLE device data.
Please help me out as I am new to Flutter. Thank you
Firstly learn basic of state management using Get you can refer to my code here what happens is every time something changes or upadtes it will immediate show in UI using specific Get Widgets like Obx and GetX , these widgets listen to changes in value which are marked with obs (observable).
for exmaple :
Obx(
() => ble.isScan.value ? LinearProgressIndicator() : SizedBox(),
),
this will observe the changes in isScan value .
class BleServices extends GetxController {
FlutterBlue blue = FlutterBlue.instance;
BluetoothDevice d;
var connectedDevices = List<BluetoothDevice>().obs;
var scanResults = List<ScanResult>().obs;
var bleState = BluetoothState.off.obs;
var isScan = false.obs;
var scanRequire = false.obs;
var bluetoothServices = List<BluetoothService>().obs;
var discoveringServices = false.obs;
var characteristics = List<BluetoothCharacteristic>().obs;
#override
void onInit() async {
super.onInit();
final perb = await Permission.bluetooth.status.isGranted;
final perL = await Permission.location.status.isGranted;
if (perb && perL) {
getConnectedDevices();
} else {
await Permission.bluetooth.request();
await Permission.location.request();
}
isScanning();
state();
}
isDiscovering() async {}
getConnectedDevices() async {
final connectedDevice = await blue.connectedDevices;
connectedDevices.value = connectedDevice;
AppLogger.print('connected devices : $connectedDevice');
if (connectedDevice.length == 0) {
scanRequire.value = true;
searchDevices();
}
return connectedDevice;
}
searchDevices() {
// AppLogger.print('pppppppppppppp');
blue
.scan(timeout: Duration(seconds: 20))
.distinct()
.asBroadcastStream()
.listen((event) {
AppLogger.print(event.toString());
scanResults.addIf(!scanResults.contains(event), event);
});
Future.delayed(Duration(seconds: 20), () {
blue.stopScan();
Get.showSnackbar(GetBar(
message: 'scan is finished',
));
});
}
isScanning() {
blue.isScanning.listen((event) {
AppLogger.print(event.toString());
isScan.value = event;
});
}
state() {
blue.state.listen((event) {
AppLogger.print(event.toString());
bleState.value = event;
});
}
}

Simultaneous dynamic async calls

I have a dynamic list of strings including codes.
I want to fetch events for each code and then yield to next state.
I have tried Future.wait() but since is dynamic I can not pass a list of async calls.
This is my code so far:
var packagesData = List<BuiltList<Event>>();
for (var packageNumber in packages) {
final packageEvents = await serviceDataSource.fetchPackageEvents(packageNumber);
packagesData.add(packageEvents);
if (packagesData.length == packages.length) {
return persistAllPackagesData(packagesData);
}
}
I wanted to do something similar to a Promise:
Promise.all([
// for loop here
]).then(() => {
console.log('I did everything!');
});
Use a taskList to maintain needed task and use Future.wait
List<Future<int>> taskList = [];
taskList.add(getRandomNumber());
...
Future.wait(taskList)
.then
full code
import 'dart:async';
import 'dart:math';
Future<int> getRandomNumber() async {
var random = new Random();
return random.nextInt(100);
}
void findSmallestNumberInList(List<int> lst) {
print("all numbers are in:");
lst.forEach((l) => print(l));
lst.sort();
int largest = lst.first;
print("The smallest random # we generated was: ${largest}");
}
void main() async {
List<Future<int>> taskList = [];
taskList.add(getRandomNumber());
taskList.add(getRandomNumber());
taskList.add(getRandomNumber());
Future.wait(taskList)
.then((List<int> results) => findSmallestNumberInList(results));
Future.wait([getRandomNumber(), getRandomNumber(), getRandomNumber()])
.then((List<int> results) => findSmallestNumberInList(results));
}