I'm looking a for a way to programmatically add a transformer to an existing stream that's already being listen to.
Example:
Stream numbers = new Stream.fromIterable([0,1,2,3]);
numbers.listen((number) => print(number));
Now in response to some UI event, I'd like to modify this stream by adding a mapping transformer, as if I originally wrote:
numbers.where((number) => number % 2 == 0);
All existing listeners should from now own only receive even numbers, without interruption. How can this be done?
Instead of thinking about it like "how do I dynamically insert a transformer into a stream", one possible way is to think about it like "how do I dynamically control a transformer that I already injected".
Here's an example of using a StreamTransformer:
var onlySendEvenNumbers = false; // controlled by some UI event handler
var originalStream = makeStreamOfStuff();
originalStream = originalStream.transform(new StreamTransformer.fromHandlers(
handleData: (int value, EventSink<int> sink) {
if (onlySendEvenNumber) {
if (value.isEven) {
sink.add(value);
}
} else {
sink.add(value);
}
}));
originalStream.listen(print); // listen on events like normal
One way I can think of doing that is filtering the Stream with a function that calls another function:
var filter = (n) => true;
Stream numbers = new String.fromIterable([0, 1, 2, 3]).where((n) => filter(n));
Then, when you want to change the filtering:
filter = (n) => n % 2 == 0;
A concrete example:
import 'dart:async';
main() {
var filter = (n) => true;
Stream numbers = new Stream.periodic(new Duration(seconds: 1), (n) => n)
.where((n) => filter(n));
numbers.listen((n) => print(n));
new Future.delayed(new Duration(seconds: 4)).then((_) {
filter = (n) => n % 2 == 0;
});
}
This will print:
0
1
2
3
4
6
8
10
12
And so on, for even numbers only, after 4 seconds.
What about rxdart's combineLatest2 ?
It combine two streams, and emit each time when changed both streams.
You can use Switch class for switch on/off with conditions.
class XsBloc {
Api _api = Api();
BehaviorSubject<List<X>> _xs = BehaviorSubject();
BehaviorSubject<Switcher> _switcher =
BehaviorSubject<Switcher>.seeded(Switcher(false, []));
XsBloc() {
Observable.combineLatest2<List<X>, Switcher, List<X>>(
_api.xs(), _switcher, (xs, s) {
if (s.isOn == true) {
return xs.where((x) => s.conditions.contains(x.id)).toList();
} else {
return xs;
}
}).listen((x) => _xs.add(x));
}
Stream<List<X>> get xs => _xs;
ValueObservable<Switcher> get switcher =>
_switcher.stream;
Function(Switcher) get setSwitcher => _switcher.sink.add;
}
class Switcher {
final bool isOn;
final List<String> conditions;
Switcher(this.isOn, this.conditions);
}
var bloc = XsBloc();
bloc.setSwitcher(true, ['A', 'B']);
bloc.setSwitcher(false, []);
bloc.setSwitcher(true, []);
Related
Is it possible to make a stream in Dart to load balance its emited datas?
Example:
I have one Stream and 3 listeners (A), (B) and (C):
Emits 0, (A) receives but not (B) and (C)
Then it emits 1, (B) receives but not (A) and (C)
Then it emits 2, (C) receives but not (A) and (B)
How to acomplish it in Dart ?
This is just a draft solution but you can do something like this (and properly add some utility methods like e.g. removing streams when they are stopping listening):
import 'dart:async';
void main() {
final splitter = StreamSplitter(
Stream<int>.periodic(const Duration(seconds: 1), (i) => i));
splitter
.getLoadBalancedStream()
.forEach((element) => print('Called Stream 1 with: $element'));
splitter
.getLoadBalancedStream()
.forEach((element) => print('Called Stream 2 with: $element'));
splitter
.getLoadBalancedStream()
.forEach((element) => print('Called Stream 3 with: $element'));
// Called Stream 1 with: 0
// Called Stream 2 with: 1
// Called Stream 3 with: 2
// Called Stream 1 with: 3
// Called Stream 2 with: 4
// Called Stream 3 with: 5
// Called Stream 1 with: 6
}
class StreamSplitter<T> {
List<StreamController<T>> streamControllers = [];
StreamSplitter(Stream<T> stream) {
final controller = _controllers().iterator;
stream.listen((event) => controller
..moveNext()
..current.add(event));
}
Stream<T> getLoadBalancedStream() {
final controller = StreamController<T>();
streamControllers.add(controller);
return controller.stream;
}
Iterable<StreamController<T>> _controllers() sync* {
// ignore: literal_only_boolean_expressions
while (true) {
for (var i = 0; i < streamControllers.length; i++) {
yield streamControllers[i];
}
}
}
}
Let's assume I have a Stream<int> emitting integers in different time deltas i.e. between 5ms and 1000ms.
When the delta is <= 50ms I want to merge them. for example:
3, (delta:100) 5, (delta:27) 6, (delta:976) 3
I want to consume: 3, 11(merged using addition), 3.
Is this possible?
You can use the debounceBuffer stream transformer from the stream_transform package.
stream
.transform(debounceBuffer(const Duration(milliseconds: 50)))
.map((list) => list.fold(0, (t, e) => t + e))
You can write that easily enough yourself:
Stream<int> debounce(
Stream<int> source, Duration limit, int combine(int a, int b)) async* {
int prev;
var stopwatch;
await for (var event in source) {
if (stopwatch == null) {
// First event.
prev = event;
stopwatch = Stopwatch()..start();
} else {
if (stopwatch.elapsed < limit) {
prev = combine(prev, event);
} else {
yield prev;
prev = event;
}
stopwatch.reset();
}
}
// If any event, yield prev.
if (stopwatch != null) yield prev;
}
I'm new to reactive programming and have difficulties using "everything can be a stream" mantra. I'm considering the following scenario - I have a stream of websocket events definied like this:
Rx.Observable.create((observer) => {
io.on('connect', function(socket){
socket.on("enroll", function(player) {
observer.next({
event: 'enroll',
player,
socket
});
});
socket.on('resign', function(player){
observer.next({
event: 'resign',
player,
socket
});
});
});
return {
dispose: io.close
};
});
Then I can do something like
enrollmentStream = events$
.filter(find({ event: "enroll" }))
.map(pick('player'));
And likewise
resignationStream = events$
.filter(find({ event: "resign" }))
.map(pick('player'));
I would like to gather enrolled players in a stream which would batch them in 4's but obviously this should be done only for users which are in enrollment stream but are not in resignationStream or at least the last event was enrollment. How do I do this?
Here is the marble diagram.
There are 5 players which enroll. Game starts when there are 4 players enrolled. Note that the second player (violet one) enrolls but then resigns so the game does not start with blue marble but with the next - yellow - cause only after that there are really 4 players ready.
Probably there should be some stream operation like "without"... is there?
I think in this scenario you could use combineLatest() and scan() operators and then make a list of unresigned players yourself:
const bufferedEnrollment = enrollmentStream.scan((acc, val) => { acc.push(val); return acc; }, []);
const bufferedResignation = enrollmentStream.scan((acc, val) => { acc.push(val); return acc; }, []);
Observable.combineLatest(bufferedEnrollment, bufferedResignation)
.map(values => {
const enrolled = values[0];
const resigned = values[1];
// remove resigned players from `enrolled` array
return enrolled;
})
.filter(players => players.length === 4)
.subscribe(...)
The scan() operator is used only to collect players into an array. If you for example wanted to be able to reset the array you could merge it with another Observable.
enrollmentStream
.merge(resetStream)
.scan((acc, val) => {
if (!val) {
return [];
}
acc.push(val);
return acc;
}, []);
(for obvious reasons I didn't test this code).
I have two streams, Stream<A> and Stream<B>. I have a constructor for a type C that takes an A and a B. How do I merge the two Streams into a Stream<C>?
import 'dart:async' show Stream;
import 'package:async/async.dart' show StreamGroup;
main() async {
var s1 = stream(10);
var s2 = stream(20);
var s3 = StreamGroup.merge([s1, s2]);
await for(int val in s3) {
print(val);
}
}
Stream<int> stream(int min) async* {
int i = min;
while(i < min + 10) {
yield i++;
}
}
See also http://news.dartlang.org/2016/03/unboxing-packages-async-part-2.html
prints
10
20
11
21
12
22
13
23
14
24
15
25
16
26
17
27
18
28
19
29
You can use StreamZip in package:async to combine two streams into one stream of pairs, then create the C objects from that.
import "package:async" show StreamZip;
...
Stream<C> createCs(Stream<A> as, Stream<B> bs) =>
new StreamZip([as, bs]).map((ab) => new C(ab[0], ab[1]));
If you need to react when either Stream<A> or Stream<B> emits an event and use the latest value from both streams, use combineLatest.
Stream<C> merge(Stream<A> streamA, Stream<B> streamB) {
return streamA
.combineLatest(streamB, (a, b) => new C(a, b));
}
For people that need to combine more than two streams of different types and get all latest values on each update of any stream.
import 'package:stream_transform/stream_transform.dart';
Stream<List> combineLatest(Iterable<Stream> streams) {
final Stream<Object> first = streams.first.cast<Object>();
final List<Stream<Object>> others = [...streams.skip(1)];
return first.combineLatestAll(others);
}
The combined stream will produce:
streamA: a----b------------------c--------d---|
streamB: --1---------2-----------------|
streamC: -------&----------%---|
combined: -------b1&--b2&---b2%---c2%------d2%-|
Why not StreamZip? Because StreamZip would produce:
streamA: a----b------------------c--------d---|
streamB: --1---------2-----------------|
streamC: -------&----------%---|
combined: -------a1&-------b2%--|
Usage:
Stream<T> sA;
Stream<K> sB;
Stream<Y> sC;
combineLatest([sA, sB, sC]).map((data) {
T resA = data[0];
K resB = data[1];
Y resC = data[2];
return D(resA, resB, resC);
});
To get combined two streams when the second takes a result from the first one use asyncExpand
Stream<UserModel?> getCurrentUserModelStream() {
return FirebaseAuth.instance.authStateChanges().asyncExpand<UserModel?>(
(currentUser) {
if (currentUser == null) {
return Stream.value(null);
}
return FirebaseFirestore.instance
.collection('users')
.doc(currentUser.uid)
.snapshots()
.map((doc) {
final userData = doc.data();
if (userData == null) {
return null;
}
return UserModel.fromJson(userData);
});
},
);
}
Using rxdart, you can use CombineLatestStream to achieve what you want. Note that the new stream doesn't return any value until all streams emitted at least one event:
You can create the combined stream using CombineLatestStream.list():
import 'package:rxdart/rxdart.dart';
Stream<A> s1 = Stream.fromIterable([A()]);
Stream<B> s2 = Stream.fromIterable([B()]);
Stream<dynamic> s3 = CombineLatestStream.list<dynamic>([s1, s2])
..listen(
(value) {
A a = value[0];
B b = value[1];
},
);
Since Dart doesn't support union types, a downside of CombineLatestStream.list() is that events from streams of different types should be casted afterwards (due to List<dynamic>). Another approach is to use CombineLatestStream.combine2() (e.g. with a combiner that creates a Tuple2) to keep the types.
I encountered the following example (Example 1 below) of Futures which caused me to wonder if I could alter the way that I was handling Futures and remove all of the nested function calls that preserve order of processing, which however result in indentation which I find a bit messy.
The altered version of my program did not work however. It did not preserve the order of processing and did not “wait” for function to complete. For example, before returning from the first call (fGetUserInput), another subsequent function was called.
Why is it that in Example 1, all of the “1st level” “new Future”s processed sequentially, however in Example 2, my altered code, the order of processing is not preserved. While the call to fGetUserInput is being processed, one of the Futures that follows it is processed?
Is it perhaps that “Example 1” only “works” because all of the statements are synchronous?
I came across a reference to “runAsync”. Can that be used to achieve what I want? (process in sequence without all of the indentation).
// Example 1. Code that I encountered for Futures //
import 'dart:async';
main() {
new Future(() => print('1'))
.then((_) => print('a'))
.then((_) => print('b'));
new Future(() => print('2'))
.then((_) => print('c'))
.then((_) => print('d'));
new Future(() => print('3'))
.then((_) =>
new Future(() => print('e'))
.then((_) => print('f'))
);
new Future(() => print('4'))
.then((_) =>
new Future(() => print('g'))
.then((_) => print('d'))
);
}
The above results in the following console output order :-
1 a b 2 c d 3 4 e f g d
Which I thought made sense.
Therefore, I modified my code to test it as follows :-
// Example 2. Altered version of my code which //
// does not preserve the order of processing, //
// which is necessary for program to function. //
new async.Future(() => fGetUserInput())
.then((lInput) {
iMaxIters = int.parse(lInput[4]);
tClearTable = (lInput[5] == "y");
iDivisor = fInitialize(iMaxIters);
tgPrint = false; // printing off
sUri =
"postgres://${lInput[1]}:${lInput[2]}#localhost:5432/${lInput[3]}";
sStartTime = lInput[7];
})
.catchError((oError) => fFatal("Get User Input", oError));
new async.Future(() => fConnectToDb(sUri, sStartTime))
.then((bool tConnected) {
if (ogDb == null)
fFatal("Unable to connect to database", "");
print ("Processing database ......");
})
.catchError((oError) => fFatal("Connect to Db", oError));
new async.Future(() => fClearTable(tClearTable))
.then((sResult) => print (sResult+"\n"))
.catchError((oError) => fFatal("Clear Table", oError));
new async.Future(() => fProcessInserts(iMaxIters, iDivisor))
.then((sResult) => print (""))
.catchError((oError) => fFatal("Process Inserts", oError));
new async.Future(() => fSetupRandKeys())
.then((sResult) => print (""))
.catchError((oError) => fFatal("Setup Random Keys", oError));
new async.Future(() => fProcessUpdates(iMaxIters, iDivisor))
.then((sResult) {
String sTotValue = fFormatAmount(igGrandTotAmt, true, 2);
fPrint ("Grand Total added to database = \$${sTotValue}");
ogDb.close();
exit(0);
})
.catchError((oError) => fFatal("Process Updates", oError));
}
void fFatal (String sMessage, Error oError) {
print("\n\nFatal Error. $sMessage\n${oError}");
exit(1);
}
async.Future<String> fProcessInserts(int iMaxIters, int iDiv) {
async.Completer oCompleter = new async.Completer<String>();
int iTot = 0;
Function fLoop;
print ("\nProcessing Inserts ......");
fResetAndStartWatch();
The following is my code prior to the above changes, and the following Example 3 appears to work OK. I don't like the extent of indentation, and in situations with more function calls, that would increase the extent of indentation. I was hoping for a more elegant way to do it.
// Example 3: The original version of my code //
// which does preserve the order of processing //
void main() {
print("");
String sCheckPoint = "Get User Input";
fGetUserInput()
.then((lInput) {
int iMaxIters = int.parse(lInput[4]);
bool tClearTable = (lInput[5] == "y");
int iDiv = fInitialize(iMaxIters);
tgPrint = false; // printing off
String sUri =
"postgres://${lInput[1]}:${lInput[2]}#localhost:5432/${lInput[3]}";
sCheckPoint = "Connect to Database";
fConnectToDb(sUri, lInput[7]).then((bool tConnected) {
if (ogDb == null)
fFatal(sCheckPoint, "Unable to conenct to Db");
print ("Processing database ......");
sCheckPoint = "Clear Table";
fClearTable(tClearTable).then((sResult) {
print (sResult+"\n");
sCheckPoint = "Process Inserts";
fProcessInserts(iMaxIters, iDiv).then((sResult) {
print;
sCheckPoint = "Set-up Random Keys";
fSetupRandKeys().then((sResult) {
print;
sCheckPoint = "Process Updates";
fProcessUpdates(iMaxIters, iDiv).then((sResult) {
String sTotValue = fFormatAmount(igGrandTotAmt, true, 2);
fPrint ("Grand Total added to database = \$${sTotValue}");
ogDb.close();
exit(0);
});
});
});
});
});
})
.catchError((oError) => fFatal(sCheckPoint, oError));
}
void fFatal (String sMessage, Error oError) {
print("\n\nFatal Error. $sMessage\n${oError}");
exit(1);
}
async.Future<String> fProcessInserts(int iMaxIters, int iDiv) {
async.Completer oCompleter = new async.Completer<String>();
int iTot = 0;
Function fLoop;
print ("Processing Inserts ......");
fResetAndStartWatch();
Remember that you can chain futures, which will reduce your indentation by quite a bit.
The downside is that you don't get nested scopes, which can be useful if you have more than one value to propagate between async blocks, but that can be worked around in a few ways.
Here's you example 3 with chaining:
// Example 3 with chaining
void main() {
String checkPoint = "Get User Input";
getUserInput().then((input) {
int maxIters = int.parse(input[4]);
bool clearTable = (input[5] == "y");
int div = initialize(maxIters);
shouldPrint = false; // printing off
String uri =
"postgres://${input[1]}:${input[2]}#localhost:5432/${input[3]}";
checkPoint = "Connect to Database";
return connectToDb(uri, input[7]).then((bool connected) {
if (db == null)
fatal(checkPoint, "Unable to conenct to Db");
print ("Processing database ......");
checkPoint = "Clear Table";
return clearTable(shouldClearTable);
}).then((result) {
print (result+"\n");
checkPoint = "Process Inserts";
return processInserts(maxIters, div);
}).then((result) {
print('');
checkPoint = "Set-up Random Keys";
return setupRandKeys();
}).then((result) {
print('');
checkPoint = "Process Updates";
return processUpdates(maxIters, div);
}).then((result) {
String totValue = formatAmount(grandTotAmt, true, 2);
print("Grand Total added to database = \$${totValue}");
return db.close();
// exit(0); pretty much never call exit()
});
}).catchError((error) => fatal(checkPoint, error));
}
Edit: Oops, looking more closely I got bit by the scoping problem... I added a level of nesting just to capture the needed vars in a scope accessible by the following blocks. I'm also removing the hungarian-ish notation, because... don't do that in Dart :)