Async Future, How to send an event back to caller that a single item has been fetched? - dart

I'm making a Future method that lives inside a seperate class, that fetches a bunch XKCD comics, and puts them in a List and returns it.
And that is all fine and dandy, but I would like to notify back when a single comic has been fetched, so I can show a progress dialog, on how far we are.
This is my code:
// This is inside my class ComicManager
Future<List<ComicModel>> generateComicList() async {
List<ComicModel> comicList = new List<ComicModel>();
ComicModel latestComic = await getLatestComic();
for (var i = 1; i <= latestComic.num; i++) {
try {
http.Response response =
await http.get('https://xkcd.com/${i}/info.0.json');
Map comicmap = json.decode(response.body);
var comic = new ComicModel.fromJson(comicmap);
comicList.add(comic);
print(comic.num);
// Notify here that we have fetched a comic
} catch (ex) {
// Comic could apparently not be parsed, skip it.
}
}
return comicList;
}
How should I solve this?

There seems no particularly elegant way to do this. From some flutter code samples, it seems using VoidCallBack listeners is an accepted way.
First register callback functions in a Set
Set<VoidCallBack> listeners
Then define the callback functions you needed. And add them to the set
void fun()
//...
listeners.add(fun);//Or you can define a method to do this or simply pass the function through the constructor of this class.
Finally, write a notifyListeners function or its equivalent and call it wherever you want
void notifyListeners(){
for(final listener in listeners){
listener();
}
}
If you want callback functions to carry an argument, just change the VoidCallBack to whatever function types.

Found a solution.
I just used Streams like so:
Stream<ComicProgressModel> getAllComicsStream() async* {
// Do what you need to do here
// This will respond back when you are listening to the stream
yield stuffToYield; // Can be anything, and you can yield as many times you want
// When you reach the end of the method, the onDone method will be called.
// So if you are running a for loop, and call yield multiple times it onDone is only called the the this method ends
}
Then I can just listen to events like so:
Stream comicStream =
ComicManager().getAllComicsStream().asBroadcastStream();
StreamSubscription comicsub = comicStream.listen((onData) {
// Do what i need
});
Super easy to be honest.

Related

Where do you do CallActivityAsync in orchestration method

I have just started using durable functions and needs some advise for how to do fan out pattern correctly. I have a FTP server where from I read all the files. I want to start an Activity function for each file. As I understand it the orchestrator function will be called everytime an Activity function is being executed. I just want to read the files once. To avoid calling the code that read the files and starts the activity functions multiple times, what is the recommended approach? Is it having an activity function that that add's all the activity functions or is it using the IsReplaying property, or something different?
[FunctionName("OrchestrationMoveFilesToBlob")]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context)
{
var outputs = new List<string>();
if (!context.IsReplaying)
{
// Do you call your database here and make a call to CallActivityAsync for each row?
}
// doing it here is properly very wrong as it will be called multiple times
var tasks = new Task<string>[7];
for (int i = 0; i < 7; i++)
{
tasks[i] = context.CallActivityAsync<string>("E2_CopyFileToBlob",""); }
await Task.WhenAll(tasks);
return outputs;
}
When looking into the sample in the link below this actually calls it directly in the orchestrator function? Is this not really bad? It continue adding same activities again and again .... ?
https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup
Not sure I understand what you try to achieve but your code looks not bad so far. An orchestration is just called once (and maybe some times more for replay but this is not your problem here). From your orchestration you can call in a fan out all your activity functions (gathering a file from an ftp) each activity function one file. await Task.WhenAll(tasks) is your fan in. (you can use a List<Task> instead of the array and call .Add(task) on it if you want. In order to not edit your code I copied it here and added some comments and questions (feel free to edit here):
[FunctionName("OrchestrationMoveFilesToBlob")]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context)
{
var outputs = new List<string>();
if (!context.IsReplaying)
{
// just needed for things that should not happen twice like logging....
}
// if your work isn't a fixed list just call an activity
// which replies with the list of work here (e.g. list of filenames)
var tasks = new Task<string>[7]; // can be a List<Task> too
for (int i = 0; i < 7; i++)
{
tasks[i] = context.CallActivityAsync<string>("E2_CopyFileToBlob","");
}
await Task.WhenAll(tasks);
return outputs; // currently an empty list. What do you want to give back?
}

Dart/Flutter - "yield" inside a callback function

I need to yield a list for a function; however, I want to yield the list from within a callback function, which itself is inside the main function - this results in the yield statement not executing for the main function, but rather for the callback function.
My problem is very similar to the problem that was solved here: Dart Component: How to return result of asynchronous callback? but I cannot use a Completer because I need to yield and not return.
The code below should describe the problem better:
Stream<List<EventModel>> fetchEvents() async* { //function [1]
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.listen((snapshot) async* { //function [2]
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
yield list; //This is where my problem lies - I need to yield for function [1] not [2]
});
}
Instead of .listen which handles events inside another function you can use await for to handle events inside the outer function.
Separately - you might want to reconsider the pattern when you yield List instances that are still getting populated inside an inner stream callback...
Stream<List<EventModel>> fetchEvents() async* {
final snapshots =
Firestore.instance.collection('events').getDocuments().asStream();
await for (final snapshot in snapshots) {
// The `await .toList()` ensures the full list is ready
// before yielding on the Stream
final events = await snapshot.documents
.map((document) => EventModel.fromJson(document.data))
.toList();
yield events;
}
}
I would like to add a suggestion for improvement here. The suggested await for solution should be avoided in some cases as it is non dismissible listener and it newer stops listening so this might lead to memory leaks. You could as well use .map to transform the stream yield results like so (havent tried to compile it, but main idea should be clear):
Stream<List<EventModel>> fetchEvents() { // remove the async*
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.map((snapshot) { // use map instead of listen
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
return list; // use return instead of yield
});
}

Create a new stream from a stream in Dart

I suspect that my understanding of Streams in Dart might have a few holes in it...
I have a situation in which I'd like a Dart app to respond to intermittent input (which immediately suggests the use of Streamss -- or Futures, maybe). I can implement the behavior I want with listener functions but I was wondering how to do this in a better, more Dartesque way.
As a simple example, the following (working) program listens to keyboard input from the user and adds a div element to the document containing what has been typed since the previous space, whenever the space bar is hit.
import 'dart:html';
main() {
listenForSpaces(showInput);
}
void listenForSpaces(void Function(String) listener) {
var input = List<String>();
document.onKeyDown.listen((keyboardEvent) {
var key = keyboardEvent.key;
if (key == " ") {
listener(input.join());
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
});
}
void showInput(String message) {
document.body.children.add(DivElement()..text = message);
}
What I'd like to be able to do is to create a new Stream from the Stream that I'm listening to (in the example above, to create a new Stream from onKeyDown). In other words, I might set the program above out as:
var myStream = ...
myStream.listen(showInput);
I suspect that there is a way to create a Stream and then, at different times and places, insert elements to it or call for it to emit a value: it feels as though I am missing something simple. In any case, any help or direction to documentation would be appreciated.
Creating a new stream from an existing stream is fairly easy with an async* function.
For a normal stream, I would just do:
Stream<String> listenForSpaces() async* {
var input = <String>[];
await for (var keyboardEvent in document.onKeyDown) {
var key = keyboardEvent.key;
if (key == " ") {
yield input.join();
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
}
}
The async* function will propagate pauses through to the underlying stream, and it may potentially pause the source during the yield.
That may or may not be what you want, since pausing a DOM event stream can cause you to miss events. For a DOM stream, I'd probably prefer to go with the StreamController based solution above.
There are several methods and there is a whole package rxdart to allow all kinds of things.
Only the final consumer should use listen and only if you need to explicitly want to unsubscribe, otherwise use forEach
If you want to manipulate events like in your example, use map.
I wasn't originally planning to answer my own question but I have since found a very simple answer to this question in the dartlang creating streams article; in case it's helpful to others:
Specifically, if we'd like to create a stream that we can insert elements into at arbitrary times and places in the code, we can do so via the StreamController class. Instances of this class have an add method; we can simply use the instance's stream property as our stream.
As an example, the code in my question could be rewritten as:
import 'dart:html';
import 'dart:async';
main() async {
// The desired implementation stated in the question:
var myStream = listenForSpaces();
myStream.listen(showInput);
}
Stream<String> listenForSpaces() {
// Use the StreamController class.
var controller = StreamController<String>();
var input = List<String>();
document.onKeyDown.listen((keyboardEvent) {
var key = keyboardEvent.key;
if (key == " ") {
// Add items to the controller's stream.
controller.add(input.join());
input.clear();
} else {
input.add(key.length > 1 ? "[$key]" : key);
}
});
// Listen to the controller's stream.
return controller.stream;
}
void showInput(String message) {
document.body.children.add(DivElement()..text = message);
}
(As mentioned in the article, we need to be careful if we want to set up a stream from scratch like this because there is nothing to stop us from inserting items to streams that don't have associated, active subscribers; inserted items would in that case be buffered, which could result in a memory leak.)

How to store result of a Future for later use

Suppose our goal is to build a List which we will call storedResult.
We have a Future<List> returning function called multiple times:
Future<List> getList()...
Suppose that we will call our getList() function for the unknown multiple times and want to store the result of each List into our storedResult:
List storedResult = [];
someOtherList.forEach((element)){
getList().then((resultingList)){
storedResult.addAll(resultingList);
}
}
print(storedResult);
If we run this code, it will print as []. How do we express this so that the result is rather:
[resultingList, resultingList, ..., ]
Remember that Futures return immediately, but their associated callbacks run asynchronously. Specifically, the work that getList() does (and everything that then does) do not happen until execution returns to the event loop.
Your problem is that you're trying to print the result of an asynchronous operation synchronously, and that won't work.
If you want to use the stored result, then what you should do is something like:
Future<List<List>> globFutures(List someOtherList) {
List<Future> futures = [];
someOtherList.forEach((element) {
futures.add(getList());
});
// Automatically completes to a List containing all the
// results of all of the futures.
return Future.wait(futures);
}
main() {
var storedResults = [];
globFutures(someOtherList)
.then((List<List> results) {
storedResults = results;
// Run code in here that depends on storedResults being available.
print(storedResults);
});
// Do **NOT** use storedResults here, as it will be [].
// The Future functions have not yet run!
assert(storedResults == []);
};

Scheduling the same activity with different arguments in Amazon SWF

I am trying to schedule an activity in Amazon SWF. Initially, I used to loop through a list and schedule the activity for each value of the list. But this would invoke the activities in parallel which I did not want. So, I modified my code to do something like this:
Promise<Void> promiseArg = null;
for(Integer i : IntegerList){
Promise<Void> nextArg = activityClient.activity1(i);
promiseArg = nextArg;
}
Though code is working, I am not sure if this is the right way to do it. Any comments would be helpful.
What is the point of using promiseArg if it is unused?
If you want them to be dependent on prev method call, create an Asynchronous method and call that with promise variable.
//Main method of decider.
Promise<Integer> promiseArg = null;
Promise<Integer> nextArg = activityClient.activity1(i, 1);
for(Integer i : IntegerList){
Promise<Integer> nextArg = fun(nextArg, Promise.asPromise(i));
}
#Asynchronous
public Promise<Integer> fun(Promise<int> nextArg, int i) {
System.out.println("Testing with current value: " + Integer.toString(nextArg.get()));
return activityClient.activity1(i, nextArg.get());
}
I haven't tested it but it should work.
Apart from this, you can also try passing prev Promise variable to activity itself with #Wait annotation in the activity declaration.
Something like this,
prevArgs = activityClient.activity1(i, prevArg));
with Activity like,
XYZ activity1(int i,#Wait Promise<int> prevArgs){
//Please check if int should be used instead of Promise<int>
}

Resources