Dart StreamController - dart

Hello i have a problem while using StreamController in Dart i have create afunction make a stream cntroller
Code:
dynamic main() {
Future getData(String text) async {
var stream;
stream = StreamController();
stream.stream.listen((text) {
print(text);
});
stream.sink.add(text);
stream.sink.close();
}
getData('hello stream');
getData('hello stream');
}
When i create instance it works and i can't close stream
Output:
hello stream
hello stream
Exited
How can i close Stream

Related

Dart equivalent of BlockingCollection

I'm currently migrating an App's logic code from C# to Dart and I'm looking for a similiar collection type in Dart to C#s BlockingCollection. I basically want a queue where i can iterate infinitely. If the queue is empty it just waits until a new element is added.
Is that possible in Dart?
Best
You can use a StreamController.
Here I translated the first C# example for BlockingCollection
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class BlockingCollectionDemo
{
static async Task Main()
{
await AddTakeDemo.BC_AddTakeCompleteAdding();
}
}
class AddTakeDemo
{
// Demonstrates:
// BlockingCollection<T>.Add()
// BlockingCollection<T>.Take()
// BlockingCollection<T>.CompleteAdding()
public static async Task BC_AddTakeCompleteAdding()
{
using (BlockingCollection<int> bc = new BlockingCollection<int>())
{
// Spin up a Task to populate the BlockingCollection
Task t1 = Task.Run(() =>
{
bc.Add(1);
bc.Add(2);
bc.Add(3);
bc.CompleteAdding();
});
// Spin up a Task to consume the BlockingCollection
Task t2 = Task.Run(() =>
{
try
{
// Consume consume the BlockingCollection
while (true) Console.WriteLine(bc.Take());
}
catch (InvalidOperationException)
{
// An InvalidOperationException means that Take() was called on a completed collection
Console.WriteLine("That's All!");
}
});
await Task.WhenAll(t1, t2);
}
}
}
to dart using a StreamController instead of BlockingCollection, and Future instead of Task.
import 'dart:async';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamController
Future<void> t2 = Future(() async {
// Consume consume the StreamController
await for (final element in bc.stream) {
print(element);
}
// Exits the loop when the stream is completed/closed
print("That's All!");
});
await Future.wait([t1, t2]);
}
That said, the StreamController differs a bit from BlockingCollection in that it is not a queue. A Stream in dart by default, can only have one subscription, unless you create a broadcast stream. Stream is more like an async enumerable in C#.
If you really need a queue data structure you can use the async package, which has a StreamQueue class that you can use to wrap the stream from the StreamController.
Here is the above code modified to use a StreamQueue:
import 'dart:async';
import 'package:async/async.dart';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
// StreamQueue<T>.next
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
StreamQueue<int> queue = StreamQueue<int>(bc.stream);
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamQueue
Future<void> t2 = Future(() async {
try {
while (true) {
// Consume consume the StreamQueue
print(await queue.next);
}
} on StateError catch (e) {
// A StateError means that next was called on a completed collection
print("That's all!");
}
});
await Future.wait([t1, t2]);
}
You can also write your own queue, based on futures instead of a stream:
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
void add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _reads = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) return Future.value(_writes.removeFirst());
var completer = Completer<T>();
_reads.add(completer);
return completer.future;
}
void add(T value) {
if (_reads.isNotEmpty) {
_reads.removeFirst().complete(value);
} else {
_writes.add(value);
}
}
}
You can also consider a double-blocking queue, where the add method also "blocks" if there is no-one to accept the value yet. It's not even that hard,.
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
Future<void> add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _completers = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) {
assert(_completers.isNotEmpty);
var completer = _completers.removeFirst();
completer.complete(_writes.removeFirst());
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
return completer.future;
}
Future<void> add(T value) {
if (_writes.isEmpty && _completers.isNotEmpty) {
var completer = _completers.removeFirst();
completer.complete(value);
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
_writes.add(value);
return completer.future;
}
}
That said, if you want to use a for (... in ...)-like loop, you probably do want to go with a Stream and use await for (... in theStream).

Can I listen to a StreamController's stream multiple times (but not more than one subscription at a time)?

I am using a StreamController in dart. I would like to be able to stop listening to the stream of the controller and then start listening again. I do not require to have multiple listeners at once. I just require that I listen to the stream, then stop listening to the stream, and then establish a new listener afterwards.
I have created a minimal example where I try to cancel the initial subscription, and then listen to the stream again, but I still get a bad state error.
import 'dart:async';
void main(List<String> arguments) async {
var controller = StreamController<String>();
var sub = controller.stream.listen((event) { });
await sub.cancel();
controller.stream.listen((event) { }); // throws bad state
}
The StreamController class creates either a single-subscription stream (which you can only listen to once) or a broadcast stream (if created using StreamController.broadcast) which can be listened to multiple times.
For your described use, you'd want the broadcast variant.
You probably want to avoid sending events while that controller has no listeners (broadcast streams can emit events even when there are no listeners, they are broadcast into the void). The onCancel and onListen callbacks of a broadcast stream controller are called when the last listener is cancelled and the first (new) listener is added.
The StreamController.broadcast controller doesn't prevent you from having multiple simultaneous listeners, but that's something you should be able to avoid just by being careful.
Example:
import 'dart:async';
void main(List<String> arguments) async {
var controller = StreamController<String>.broadcast();
controller.onListen = () {
print("Active");
};
controller.onCancel = () {
print("Inactive");
};
var sub = controller.stream.listen((event) { }); // "Active"
await sub.cancel(); // "Inactive"
controller.stream.listen((event) { }); // "Active"
}
If you really want to insist on "one listener at a time", you can wrap the stream in something like:
import "dart:async";
/// Allows a stream to be listened to multiple times.
///
/// Returns a new stream which has the same events as [source],
/// but which can be listened to more than once.
/// Only allows one listener at a time, but when a listener
/// cancels, another can start listening and take over the stream.
///
/// If the [source] is a broadcast stream, the listener on
/// the source is cancelled while there is no listener on the
/// returned stream.
/// If the [source] is not a broadcast stream, the subscription
/// on the source stream is maintained, but paused, while there
/// is no listener on the returned stream.
///
/// Only listens on the [source] stream when the returned stream
/// is listened to.
Stream<T> resubscribeStream<T>(Stream<T> source) {
MultiStreamController<T>? current;
StreamSubscription<T>? sourceSubscription;
bool isDone = false;
void add(T value) {
current!.addSync(value);
}
void addError(Object error, StackTrace stack) {
current!.addErrorSync(error, stack);
}
void close() {
isDone = true;
current!.close();
current = null;
sourceSubscription = null;
}
return Stream<T>.multi((controller) {
if (isDone) {
controller.close(); // Or throw StateError("Stream has ended");
return;
}
if (current != null) throw StateError("Has listener");
current = controller;
var subscription = sourceSubscription ??=
source.listen(add, onError: addError, onDone: close);
subscription.resume();
controller
..onPause = subscription.pause
..onResume = subscription.resume
..onCancel = () {
current = null;
if (source.isBroadcast) {
sourceSubscription = null;
return subscription.cancel();
}
subscription.pause();
return null;
};
});
}

How to create a StreamTransformer in Dart?

Trying to build a custom StreamTransformer class, however a lot of the examples out there seem to be out of date, and the one found in the documentation isn't (what some typed languages might consider anyway) as a class (found here: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:async.StreamTransformer). This doesn't seem like a very Dart-like way of approaching it and rather more of a Javascript-like way (which I'm using Dart to avoid).
Many online sources say this is how you create a StreamTransformer, however there errors when extending it.
class exampleStreamTransformer extends StreamTransformer
{
//... (This won't work)
}
'Implements' seems to be the way to go, along with implementing the bind function needed:
class exampleStreamTransformer implements StreamTransformer
{
Stream bind(Stream stream)
{
//... (Go on to return new stream, etc)
}
}
I can't seem to find any examples of this way, but have thrown something together myself (which is accepted in my IDE, but isn't accepted at runtime, I get a null object error when it tries to use pause getter):
class exampleStreamTransformer implements StreamTransformer
{
StreamController<String> _controller;
StreamSubscription<String> _subscription;
Stream bind(Stream stream)
{
_controller = new StreamController<String>(
onListen: ()
{
_subscription = stream.listen((data)
{
// Transform the data.
_controller.add(data);
},
onError: _controller.addError,
onDone: _controller.close,
cancelOnError: true); // Unsure how I'd pass this in?????
},
onPause: _subscription.pause,
onResume: _subscription.resume,
onCancel: _subscription.cancel,
sync: true
);
return _controller.stream;
}
}
Would like to achieve it this way, as in the 'typed' way of producing the class, any help is much appreciated, thank you.
Why don't you use StreamTransformer.fromHandler():
import 'dart:async';
void handleData(data, EventSink sink) {
sink.add(data*2);
}
void main() {
StreamTransformer doubleTransformer = new StreamTransformer.fromHandlers(handleData: handleData);
StreamController controller = new StreamController();
controller.stream.transform(doubleTransformer).listen((data) {
print('data: $data');
});
controller.add(1);
controller.add(2);
controller.add(3);
}
Output:
data: 2
data: 4
data: 6
Okay. Here's another working example:
import 'dart:async';
class DuplicateTransformer<S, T> implements StreamTransformer<S, T> {
StreamController _controller;
StreamSubscription _subscription;
bool cancelOnError;
// Original Stream
Stream<S> _stream;
DuplicateTransformer({bool sync: false, this.cancelOnError}) {
_controller = new StreamController<T>(onListen: _onListen, onCancel: _onCancel, onPause: () {
_subscription.pause();
}, onResume: () {
_subscription.resume();
}, sync: sync);
}
DuplicateTransformer.broadcast({bool sync: false, bool this.cancelOnError}) {
_controller = new StreamController<T>.broadcast(onListen: _onListen, onCancel: _onCancel, sync: sync);
}
void _onListen() {
_subscription = _stream.listen(onData,
onError: _controller.addError,
onDone: _controller.close,
cancelOnError: cancelOnError);
}
void _onCancel() {
_subscription.cancel();
_subscription = null;
}
/**
* Transformation
*/
void onData(S data) {
_controller.add(data);
_controller.add(data); /* DUPLICATE EXAMPLE!! REMOVE FOR YOUR OWN IMPLEMENTATION!! */
}
/**
* Bind
*/
Stream<T> bind(Stream<S> stream) {
this._stream = stream;
return _controller.stream;
}
}
void main() {
// Create StreamController
StreamController controller = new StreamController.broadcast();
// Transform
Stream s = controller.stream.transform(new DuplicateTransformer.broadcast());
s.listen((data) {
print('data: $data');
}).cancel();
s.listen((data) {
print('data2: $data');
}).cancel();
s.listen((data) {
print('data3: $data');
});
// Simulate data
controller.add(1);
controller.add(2);
controller.add(3);
}
Let me add some notes:
Using implements seems to be the right way here when looking at the source code of other dart internal transformers.
I implemented both versions for regular and a broadcast stream.
In case of a regular stream you can call cancel/pause/resumt directly on the new stream controller because we can only listen once.
If you use a broadcast stream I found out that listen() is only called if there is no one listening already to the stream. onCancel behaves the same. If the last subscriber cancels its subscription, then onCancel is called. That's why it is safe to use the same functions here.
Unlike map, transformers are more powerful and allows you to maintain an internal state, and emit a value whenever you want. It can achieve things map can't do, such as delaying, duplicating values, selectively omitting some values, and etc.
Essentially, the implementation requires a bind method that provides a new stream based on an old stream being passed in, and a cast method that helps with type-checking during run-time.
Here's an over-simplified example of implementing a "TallyTransformer" that transforms a stream of integer values into a stream of sums. For example, if the input stream so far had 1, 1, 1, -2, 0, ..., the output stream would've been 1, 2, 3, 1, 1, ..., i.e. summing all inputs up to this point.
Example usage: stream.transform(TallyTransformer())
class TallyTransformer implements StreamTransformer {
StreamController _controller = StreamController();
int _sum = 0; // sum of all values so far
#override
Stream bind(Stream stream) {
// start listening on input stream
stream.listen((value) {
_sum += value; // add the new value to sum
_controller.add(_sum); // emit current sum to our listener
});
// return an output stream for our listener
return _controller.stream;
}
#override
StreamTransformer<RS, RT> cast<RS, RT>() {
return StreamTransformer.castFrom(this);
}
}
This example is over-simplified (but still works) and does not cover cases such as stream pausing, resuming or canceling. If you run into "Stream has already been listened" error, make sure streams are broadcasting.
https://github.com/dart-lang/sdk/issues/27740#issuecomment-258073139
You can use StreamTransformer.fromHandlers to easily create
transformers that just convert input events to output events.
Example:
new StreamTransformer.fromHandlers(handleData: (String event, EventSink output) {
if (event.startsWith('data:')) {
output.add(JSON.decode(event.substring('data:'.length)));
} else if (event.isNotEmpty) {
output.addError('Unexpected data from CloudBit stream: "$event"');
}
});
If you want to simply transform values using a function like this
int handleData(int data) {
return data * 2;
}
use map method of Stream
stream
.map(handleData)
.listen((data) {
print('data: $data');
});
Full example:
import 'dart:async';
int handleData(int data) {
return data * 2;
}
void main() {
final controller = StreamController<int>();
controller.stream
.map(handleData)
.listen((data) {
print('data: $data');
});
controller.add(1);
controller.add(2);
controller.add(3);
}
See more examples on dart.dev

Dart - unit test of a stream event timing out

This is a very simplified version of the problem I have encountered when trying to unit tests streams.
The test checks that the correct event has been added to the stream - it appears to work fine - for example, change the value add( 'test') to add( 'test2') will fail the test.
But when you comment out the line fireKeepAliveMessage(message); so that the event does not throw, the unit test will simply run forever.
How can I add some sort of timeout to the test? Or is there a better approach to this problem?
library stream_test;
import "package:unittest/unittest.dart";
import "dart:async";
void main() {
test("aa", () {
StreamController streamController = new StreamController();
streamController.add( "test");
Stream underTest = streamController.stream;
underTest.first.then(expectAsync((e){
expect( e, equals( "test"));
}));
});
}
I would do it like:
library stream_test;
import "package:unittest/unittest.dart";
import "dart:async";
void main() {
test("aa", () {
StreamController streamController = new StreamController();
Timer t;
Stream underTest = streamController.stream;
underTest.first.then(expectAsync((e) {
expect(e, equals("test"));
if (t != null) {
t.cancel();
}
}));
t = new Timer(new Duration(seconds: 3), () {
fail('event not fired in time');
});
streamController.add("test");
});
}
Stream class has method for this.
Stream timeout(Duration timeLimit, {Function void onTimeout(EventSinksink)})
Creates a new stream with the same events as this stream.
Whenever more than timeLimit passes between two events from this
stream, the onTimeout function is called.
The countdown doesn't start until the returned stream is listened to.
The countdown is reset every time an event is forwarded from this
stream, or when the stream is paused and resumed.
The onTimeout function is called with one argument: an
dart-async.EventSink that allows putting events into the returned
stream. This EventSink is only valid during the call to onTimeout.
If onTimeout is omitted, a timeout will just put a
dart-async.TimeoutException into the error channel of the returned
stream.
The returned stream is not a broadcast stream, even if this stream is.

How do you create a Stream in Dart?

I basically know how to use them; for instance listening to the onClick Stream of an Element.
But, how do you set up your own Streams?
Simple example
Here's a complete working example:
import 'dart:async';
import 'dart:io';
class Application {
Stream onExit;
Application() {
// Create a stream controller and assign its stream to "onExit".
var controller = new StreamController();
onExit = controller.stream;
// Create some class that uses our stream.
new UserOfStream(this);
// Whenever we exit the application, notify everyone about it first.
controller.add('we are shutting down!');
exit(0);
}
}
class UserOfStream {
UserOfStream(app) {
app.onExit.listen((String message) => print(message));
}
}
main() => new Application();
You can also do cool things like check if there are subscribers with controller.hasListener or you can signal an error. Be sure to check the API documentation on StreamController.
You can use new StreamController.broadcast() for allowing multiple listeners.
For copy-pasters
Here's a simple way to create a stream (great snippet for copy-pasters):
class Something {
StreamController _onExitController = new StreamController.broadcast();
Stream get onExit => _onExitController.stream;
}
Then the class can just access _onExitController to control the stream (to for example .add()).
In addition to StreamController you can instantiate a Stream directly with one of its named constructors:
Stream.fromFuture() Returns a stream that fires one event (whatever the Future completes to.)
Stream.fromIterable() Returns a stream that converts the Iterable elements to a sequence of events.
Stream.periodic() Returns a stream that fires a computed event periodically.
This is very handy as you can write code that expects to consume a stream, but you have multiple choices as to how to feed events to that class. For example: Stream.fromIterable() could be used in a unit test to fire a known sequence of events to a class that otherwise normally would be fed data events read from a file.
I just created a new Dart library called event_stream to make creating custom events on your classes easier. Here is an example:
class ClassWithEvents implements NotifyPropertyChanged {
String _someProperty;
final EventStream<PropertyChangedEventArgs> _onPropertyChangedEvent = new EventStream<PropertyChangedEventArgs>();
Stream<PropertyChangedEventArgs> get onPropertyChanged => _onPropertyChangedEvent.stream;
final EventStream _onClosedEvent = new EventStream();
Stream get onClosed => _onClosedEvent.stream;
String get someProperty => _someProperty;
set someProperty(String value) {
_onPropertyChangedEvent.signal(new PropertyChangedEventArgs('someProperty', value));
_someProperty = value;
}
close() {
_onClosedEvent.signal();
}
}
main() {
var c = new ClassWithEvents();
c.onPropertyChanged.listen((PropertyChangedEventArgs<String> args) => print('changed: name=${args.propertyName} value=${args.value}'));
c.onClosed.listen((_) => print('closed'));
c.someProperty = "test";
c.close();
}
There is sample with from flutter bloc
Add dependency
rxdart: ^0.27.2
Create stream controller
final _todoStreamController = BehaviorSubject<List>.seeded(const []);
Update when there is a change
Future saveTodo(Todo todo) {
final todos = [..._todoStreamController.value];
final todoIndex = todos.indexWhere((t) => t.id == todo.id);
if (todoIndex >= 0) {
todos[todoIndex] = todo;
} else {
todos.add(todo);
}
_todoStreamController.add(todos);
}
Broadcast
Stream<List> getTodos() => _todoStreamController.asBroadcastStream();
Subscribe
Future _onSubscriptionRequested(
TodosOverviewSubscriptionRequested event,
Emitter emit,
) async {
emit(state.copyWith(status: () => TodosOverviewStatus.loading));
await emit.forEach<List<Todo>>(
_todosRepository.getTodos(),
onData: (todos) => state.copyWith(
status: () => TodosOverviewStatus.success,
todos: () => todos,
),
onError: (_, __) => state.copyWith(
status: () => TodosOverviewStatus.failure,
),
);
}
REF LINK

Resources