Generate a stream from another stream result - dart

I have a stream I want to map the result of that stream to another stream and return the mapped stream.
Stream<SomeClass> subscribe() async* {
final Stream<Map<String, dynamic>> baseStream = api.subscribeToSomething(id: id);
baseStream.listen(
(Map<String, dynamic> response) {
if (response.containsKey('error')) {
throw Exception(response['error']['message']);
} else {
yield SomeClass.fromMap(response['result']);
}
},
);
}
but I get this error:
The method 'yield' isn't defined for the class 'SomeClass'. Try
correcting the name to the name of an existing method, or defining a
method named 'yield'.
question is how can I map a stream to another stream and return result stream?

Thanks to julemand101, the solution is:
Stream<SomeClass> subscribe() =>
api.subscribeToSomething(id: id).map<SomeClass>(
(Map<String, dynamic> response) {
if (response.containsKey('error')) {
throw Exception(response['error']['message']);
} else {
return SomeClass.fromMap(response['result']);
}
},
);

Use an await-for to listen for events:
Stream<SomeClass> subscribe() async* {
final Stream<Map<String, dynamic>> baseStream = api.subscribeToSomething(id: id);
await for (var response in baseStream) {
if (response.containsKey('error')) {
throw Exception(response['error']['message']);
} else {
yield SomeClass.fromMap(response['result']);
}
}
}
The await for will forward pauses and resumes correctly to the base stream, and it will make errors in the base stream terminate the loop.
(Also, consider creating a subclass of Exception for your excepsions, so your users can catch and handle those specifically, rather than having to catch all exceptions).

Related

FUtureProvider was disposed before value was emitted

I wrote the following code and encountered the error The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
I thought it was strange, so I debugged FutureProvider's onDispose and found that it was disposed during the await of the API call, which is confirmed by the output of disposed!
class HogeNotifier extends StateNotifier<Hoge> {
onFormSubmitted(String input) async {
final value = await _reader(searchProvider(input).future); // The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
// execute by using value
}
}
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.onDispose(() {
print("disposed!");
});
try {
final result = await dataSource.find(keyword: value); //call api asynchronously
return Future.value(result);
} on Exception catch (e) {
return Future<Data>.error(e);
}
});
How can I solve this problem?

Why omitting the await keyword results in different behavior?

I am testing some middleware code. When the function middleware.call is provoked I get different results depending on whether I use the keyword await when calling it.
This is testing code:
final actionLog = <dynamic>[];
final Function(dynamic) next = (dynamic action){
actionLog.add(action);
};
test('Fetching a dictionary entry successfully', () async {
DictionaryEntry dictionaryEntry = new DictionaryEntry(2333, 'after', null, null);
when(
mockApi
.getDictionaryEntry('after', any, any)
).thenAnswer((_) => Future.value(dictionaryEntry));
final action = FetchDictionaryEntryAction('after');
await dictionaryMiddleware.call(mocksStore, action, next);
// length of action log is 2
// when I don't use await before calling dictionaryMiddleware.call
// but its 4 when using await before calling the function.
print("length of action log " + actionLog.length.toString());
print(actionLog);
});
This is the middleware:
#override
Future<void> call(
Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
if(action is FetchDictionaryEntryAction){
await _fetchDictionaryEntry(action.speechText,
action.fromLanguage, action.toLanguage, next
);
}
}
Future<void> _fetchDictionaryEntry(
String speechText,
String fromLanguage,
String toLanguage,
NextDispatcher next
) async {
if(speechText == null || speechText?.length == 0){
next(ReceivedDictionaryEntryAction(dictionaryEntry: null));
return;
}
next(RequestingDictionaryEntryAction());
try{
final DictionaryEntry dictionaryEntry =
await this.api.getDictionaryEntry(speechText, fromLanguage, toLanguage);
next(ReceivedDictionaryEntryAction(dictionaryEntry: dictionaryEntry));
next(CacheDictionaryEntryAction(dictionaryEntry));
}catch(e){
next(ErrorLoadingDictionaryEntryAction);
}
}
I am wondering why does next() (the dispatcher in the middleware) get called only 2 times when not using await as apposed to getting called 4 times when using await which is the normal behavior.

Future.then() is executed too early

I have a method which is supposed to return a Future, containing a list of groups.
This works fine as I can loop the list of groups in that method itself, but somehow list is returned before it could be filled. Surely this is an error on my part but I can't seem to grasp what I'm doing wrong.
Future< List<GroupData> > getGroups(String uniqueUserID) async
List<GroupData> groups = new List<GroupData>();
try {
var result = Firestore.instance
.collection("groups")
.where("members", arrayContains: uniqueUserID);
result.snapshots()
.listen (
(data) {
// Handle all documents one by one
for (DocumentSnapshot ds in data.documents)
{
List<String> members = new List<String>();
for (dynamic member in ds.data['members'])
{
members.add( member );
}
groups.add( new GroupData(ds.data['name'], ds.data['description'], ds.documentID.toString(), ds.data['admin'], members) );
}
}
);
} catch (exception)
{
print ('Something went wrong while fetching the groups of user ' + uniqueUserID + ' !');
}
return groups;
}
This method is being called using the method Future.then() but the list is empty while there should be several resuls (and there are, I can loop all items in the list in the above method and access/print their data). What am I missing?
The execution of your function is never locked. It doesn't wait until your listened stream finished.
There are a few solutions:
change stream.listen into await for (final item in stream)
add an await stream.done
Example:
before:
Stream<List<T>> stream;
stream.listen((list) {
for (final item in list) {
print(item);
}
});
after:
await for (final list in stream) {
for (final item in list) {
print(item);
}
}

What is the best/shortest way to convert an Iterable to a Stream, in Dart?

I have an Iterable, and I'd like to convert it to a Stream. What is the most efficient/shortest-amount-of-code to do this?
For example:
Future convert(thing) {
return someAsyncOperation(thing);
}
Stream doStuff(Iterable things) {
return things
.map((t) async => await convert(t)) // this isn't exactly what I want
// because this returns the Future
// and not the value
.where((value) => value != null)
.toStream(); // this doesn't exist...
}
Note: iterable.toStream() doesn't exist, but I want something like that. :)
Here's a simple example:
var data = [1,2,3,4,5]; // some sample data
var stream = new Stream.fromIterable(data);
Using your code:
Future convert(thing) {
return someAsyncOperation(thing);
}
Stream doStuff(Iterable things) {
return new Stream.fromIterable(things
.map((t) async => await convert(t))
.where((value) => value != null));
}
In case you are using the Dart SDK version 1.9 or a newer one, you could easily create a stream using async*
import 'dart:async';
Future convert(thing) {
return new Future.value(thing);
}
Stream doStuff(Iterable things) async* {
for (var t in things) {
var r = await convert(t);
if (r != null) {
yield r;
}
}
}
void main() {
doStuff([1, 2, 3, null, 4, 5]).listen(print);
}
Maybe it is easier to read as it has less braces and "special" methods, but that is a matter of taste.
If you want to sequentially process each item in the iterable you can use Stream.asyncMap:
Future convert(thing) {
return waitForIt(thing); // async operation
}
f() {
var data = [1,2,3,4,5];
new Stream.fromIterable(data)
.asyncMap(convert)
.where((value) => value != 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

Resources