How to invoke streams from within a stream in Dart - flutter

I want to output progress messages, while I download files and populate my database in my flutter app.
For this, I am using a StreamBuilder and a Stream that yields status message updates while performing work.
So far so good, but now I would like to show some details about the download progress in addition to my status messages. As far as I understand, I can wrap the download progress in another stream, but now I am a little stuck as to how I would invoke that stream from within my status updating stream.
The code looks like this right now:
Stream<String> _upsertResult(context) async* {
yield "Downloading Identifiers... (1/6)";
await IdentifierService.downloadIdentifiers();
yield "Upserting Identifiers... (2/6)";
await IdentifierService.upsertIdentifiers();
yield "Downloading Locations... (3/6)";
await LocationService.downloadLocations();
yield "Upserting Locations... (4/6)";
await LocationService.upsertLocations();
yield "Downloading Identifiables... (5/6)";
await IdentifiableService.downloadIdentifiables();
yield "Upserting Identifiables... (6/6)";
await IdentifiableService.upsertIdentifiables();
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => CurtainInformationScreen()),
);
});
}
Right now downloadIdentifiers() is implemented as a Future, but I could rewrite it as a Stream, in order to be able to yield download progress status updates.
I think I can listen to a new Stream I create and re-yield it in my _upsertResult Stream, but I am wondering if there is a more elegant solution to my problem here, like waiting for the Stream to end, but re-yielding all results from the Stream as it runs.

I followed pskink's advice and did it like this:
yield "Downloading Identifiers... (1/6)";
yield* IdentifierService.downloadIdentifiers();
as according to http://dart.goodev.org/articles/language/beyond-async#yield
The yield* (pronounced yield-each) statement is designed to get around
this problem. The expression following yield* must denote another
(sub)sequence. What yield* does is to insert all the elements of the
subsequence into the sequence currently being constructed, as if we
had an individual yield for each element. We can rewrite our code
using yield-each as follows:
Then I can just yield inside downloadIdentifiers and the value will be handed through.

Related

Flutter - Waiting for an asynchronous function call return from multiple synchronous function calls

I have an async function which is called multiple times synchoronusly.
List response = await Future.wait([future, future])
Inside, it popups a form and waiting for it to be submitted or cancelled.
var val = await Navigator.push(
context,
MaterialPageRoute(builder : (context) => const TheForm())
);
The first served Future will popup the form first and waiting for the return. No problem with that. But I want the second Future to check first if the form is already popped up. If it is, it just waiting for it to conclude and receive the same returned value.
I'm aware that receiving same function return from two calls sounds crazy and impossible. I'm just looking for a way to hold the second Future call on and trigger to conclude it from somewhere else.
Kindly tell me what I was missing and I'll provide the required information.
I try to use ValueNotifier's. Unfortunately ValueNotifier.addListener() only accept a VoidCallback. As for now, this is my solution. Still looking for a better way to replace the loop.
Future future() async{
if(ison) await Future.doWhile(() async {
await Future.delayed(Duration(seconds: 1));
return ison;
});
else{
ison = true;
result = ... //Popup form
ison = false;
}
return await result;
}
It sounds like you want to coalesce multiple calls to an asynchronous operation. Make your asynchronous operation cache the Future it returns and make subsequent calls return that Future directly. For example:
Future<Result>? _pending;
Future<Result> foo() {
if (_pending != null) {
return _pending!;
}
Future<Result> doActualWork() async {
// Stuff goes here (such as showing a form).
}
return _pending = doActualWork();
}
Now, no matter how many times you do await foo();, doActualWork() will be executed at most once.
If you instead want to allow doActualWork() to be executed multiple times and just to coalesce concurrent calls, then make doActualWork set _pending = null; immediately before it returns.

misleading concept of future and async programming in flutter

I found it a bit confused with the concept of Future and async programming.
By definition, Future is a type that async function will return in Future.
The purpose is that we want the program to keep running while it is still waiting for the result of the async function.
What I dont understand is that, every often/always, I saw people using async with await which stop proceeding the program until it gets the result from async function called.
Arent we come to full circle? At first, async comes in for the situation that we dont want to wait for program taking time. But now, we use async with await in which we wait until the result is there
It is not always necessary to use await with a future. await can be used if you want to do further processing with the data.
Example:
Future<int> _getInt()async{
Future.delay(Duration(seconds:3)); //simulating network delay
return 7;
}
void _add() async{
int res = await _getInt() + 10; //need to await because we are going to use a future variable
_putInt(res); //not nesscary to await if you don't want to handle the response
/* Ex: var result = await _putInt(); // if you want to handel the response
if (result.statusCode==200){
// handle success
}else{
// handle error
}*/
}
Future _putInt(int number)async{
var res = await http.post('url',body:{'data':number});
return res;
}
Well, your institution is right, we use Future and async as it is nonblocking and follows an event loop approach, meaning there is a callback to the Future when it is ready to execute.
Coming to your point, I have done this myself a lot of time. And neither approach is wrong.
But when it comes to Flutter, it is in your best interest that you don't do anything else when running an async function because dart is single-threaded.
This can be blocking sometimes depending on the work of the function.
This can also be simply for UX as some data would be critical to your application and you shouldn't allow the user to do anything else until it is loaded.

flutter: how to use 'await for' to wait for other BLoC event to finish

on screen init, I am loading my data via an externalData_bloc. On the same screen I am also rendering another widget controlled with internalData_bloc for user input which depends on the number of imported records (how many rows are needed). Of course, the rendering is faster than the data load, so I get null rows needed.
I found this nice question & answer, however, I do not get it to work. The solution says
Future loginProcess() async {
await for (var result in _userBloc.state) {
if (result.status == Status.finished) {
return;
}
}
}
I am within my repository. In here, I am also storing the external data in a variable. So within the repository class I have my function to get the number of records (properties are stored in the repository, and I want to return its length).
Future<int> getNoOfProperties(int problem) async {
LogicGraphsPStmntBloc bloc = LogicGraphsPStmntBloc();
Future propertiesLoad() async {
await for (var s in bloc) {
if (s == LogicGraphsPStmntLoadSuccess) {
return;
}
}
}
propertiesLoad();
return properties.length;
}
But no matter what I try to put in for await for (var result in XXXX) or if (s.YYY == ZZZ), it doesn't work. My understanding is, XXXX needs to be a stream which is why I used bloc = LogicGraphsPStmntBloc(). But I guess this creates another instance than the one used in my widgets. LogicGraphsPStmntBloc doesn't work either, nor do states. bloc at least doesn't throw an error already in the editor, but besides the instance issue, the if statement throws an error, where in cubit.dart StreamSubscription<State> listen(... is called with cancelOnError . Anyone having some enlightenment?
Bloc uses Stream and has a continuous flow of data. You might want to listen for changes in data instead of a finished task. Using a StreamBuilder is one way of doing this.
StreamBuilder<User>(
stream: bloc.userState, // Stream from bloc
builder: (context, AsyncSnapshot<State> snapshot) {
if (snapshot.hasData) {
// check if auth status is finished
}
}
)

How do I make a Stream version of Future.wait in Dart?

I want to run Future.wait() on some network calls so they can run concurrently. I want to speed this up even further my returning the results as they become available so I can start displaying them. I figured just a small modification to the Future.wait call would be all I need, but unfortunately, it uses private classes internally. Is there a better way to accomplish this?
Future.wait dart documentation
You can use Stream.fromFutures constructor
The stream reports the results of the futures on the stream in the
order in which the futures complete. Each future provides either a
data event or an error event, depending on how the future completes.
If some futures have already completed when Stream.fromFutures is
called, their results will be emitted in some unspecified order.
When all futures have completed, the stream is closed.
Stream<int> streamingInts = Stream.fromFutures([
Future.delayed(Duration(seconds: 1), () => 1),
Future.delayed(Duration(seconds: 2), () => 2),
]);
await for (final i in streamingInts) {
print(i);
}
My real use case - to show that loading takes more time than expected
if my real api call ends fast I just yield LoadedState & stop listen to stream
if my real api call is too slow special Future.delayed will fire special state LoadingTakesTooLongState
Stream<BlocState> loadingStates = Stream.fromFutures([
Future.delayed(_kTooSlowDelay, () => LoadingTakesTooLongState()),
_loadData(),
]);
await for (var state in loadingStates) {
yield state;
if (state is LoadedState) break;
}
Future<LoadedState> _loadData() ...

How to navigate to a new screen after data is fetched from API in Flutter?

In my main.dart file I have a Future that fetches data from an API (json file).
What is the proper way that I can navigate to a new screen as soon as Future finishes fetching data?
You can do it like this:
yourFutureObject.then((){
//write the code you want to run as soon as your Future finishes
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) =>
YourNewPage()));
});
I used some general names because you didn't post any code but you can write the function you want to run when Future finished and pass it to Future’s then method
So you have a Method that returns a Future
bool asyncResult2 = await asyncFunc2();
if(asyncResult2)
{
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
asyncFunc2 funtion when completes the data fetching and assigning (or anything else) then all of these is done you can just pass the Future of boolean value true which states that every thing was done sucessfully and you can proceed to the second page or else if the the boolean value is false then there was something wrong with fetching or assigning