Future.then() is executed too early - dart

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);
}
}

Related

Dart Streams - How to combine previous and next element?

I have a simple widget subscribed to a Stream of elements.
Each time a new element is received I would like to get also the previous element and decide which one of them pass downstream.
Currently I am using the map operator to store the previous element and calculate the next, like this:
elements.map((e) {
if (this.previous == null) {
this.previous = e;
return e;
}
final next = merge(this.previous, e);
this.previous = e;
return next;
}).listen(...);
How can I do this better and avoid having this.previous?
If you use the rxdart package there is an extension method called pairwise which according to the documentation:
Emits the n-th and n-1th events as a pair. The first event won't be emitted until the second one arrives.
Then you should be able to do something along the lines of this:
elements.pairwise().map((pair) => merge(pair.first, pair.last)).listen(...);
Here is one possibility
void main() {
List list = [12, 24, 48, 60];
list.reduce((value, element) {
print(value + element); // Push to another list maybe?
return element;
});
}
If you are working with a stream try this
void main() {
var counterStream = Stream<int>.periodic(const Duration(seconds: 1), (x) => x)
.reduce((previous, element) {
print(previous + element); // Push to another stream maybe?
return element;
});
}

Stream of List with elements from another streams' current values

I want to create a List Stream based on another elements Stream. The List Stream should yield a new list every time an element from the list emits a new value.
Something like this:
Stream<List<Model>> getListStream(List<int> ids) async* {
final List<Model> models = [];
for (var id in ids) {
getModelStream(id).listen((event) {
models.add(event);
});
}
yield models;
}
but it always yields an empty array.
I think the problem is probably the fact the it does not react to the event listener.
How do you deal with this kind of problem?
You're yielding an empty list which will later be filled in.
If you want to wait for the list to be filled in before yielding it, you'll have to do that.
Either:
Stream<List<Model>> getListStream(List<int> ids) async* {
for (var id in ids) {
yield await getModelStream(id).toList();
}
}
or, if you want to start all the streams immediately, and compute them in parallel, and then emit the values when they are done (not necessarily in the original ID order), then:
Stream<List<Model>> getListStream(List<int> ids) {
var controller = StreamController<List<Model>>(sync: true);
controller.onListen = () {
var count = 0;
for (var id in ids) {
count++;
getModelStream(id).toList().then((models) {
controller.add(models);
if (--count == 0) controller.close();
});
}
};
return controller.stream;
}

Generate a stream from another stream result

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).

How to select an item from a List in flutter

I have list from a model like this
amount:"12000"
dateTime:"19/07/2018"
detail:"Soto"
hashCode:853818549
id:1
name:"Theodorus"
I want to just select amount and add it to another list of string, but I'm always getting this error A value of type 'String' can't be assigned to a variable of type 'List<String>'. , I thinks its because im not doing it right, here is my code below
void setupList() async {
DebtDatabase db = DebtDatabase();
listCache = await db.getMyDebt();
setState(() {
filtered = listCache;
});
List<String> amount = new List<String>();
listCache.map((value) {
amount = value.amount; } );
//print(amount);
}
can anyone help me, so I can get list of ammount from this model list and then sum all the ammount?
The map function returns an iterable and you can then transform it into a list.
You should try something like this:
void setupList() async {
DebtDatabase db = DebtDatabase();
listCache = await db.getMyDebt();
setState(() {
filtered = listCache;
});
List<String> amount = listCache.map((value) => value.amount).toList();
//print(amount);
}

Request data from a stream one item at time?

I'm trying to fetch data from a REST endpoint that serves a paginated response. On a button click in flutter, I would like to get the next item in the response. I think I want to use a Stream that abstracts away the paginated nature of the request, and automatically polls the next result.
Something like this dartish pseudo-code:
Stream<String> nextUrl(int lastPage=0)
{
// Get paginated response
batch = server.getResponse(lastPage)
for (item in batch)
{
yield item;
}
// Recurse automatically
// Not a problem if the stream is suspended
// after every item request. A big problem
// if the stream never pauses.
await for (String item in nextUrl(lastPage++)
{
yield item;
}
}
// In flutter
class WidgetState extends state<MyPage> {
Stream<String> urlStream = new nextUrl(0);
String currentItem;
...
#override
Widget build(BuildContext context) {
return new InkWell(
onTap: () {
(() async {
// This is the call I haven't figured out.
await item = urlStream().getNext();
setState(() { currentItem = item; });
})();
}
);
}
}
I get the feeling that maybe something like Stream.getNext() doesn't exist? That I should be pausing / unpausing this stream? But I'm not sure how that would return only a single item at a time.
The async package provide StreamQueue that allows to do that
var events = new StreamQueue<String>(yourStream);
var first = await events.next;
while (...) {
...
first = await events.next;
}

Resources