I am using flutter_bloc library.
In the bloc, the mapEventToState method is an async* function which returns Stream<BlocState>.
From this function I am calling other async* functions like this yield* _handleEvent(event)
In such method, I am calling some Future returns functions but in the Future then() function it wont let me call other yield* functions.
Here is an example:
Stream<BlocState> mapEventToState(BlocEvent event) async*{
yield* _handlesEvent(event); //This calls to worker method
}
Stream<BlocState> _handleEvent(BlocEvent event) async* {
_repository.getData(event.id).then((response) async* { //Calling Future returned function
yield* _processResult(response); //This won't work
}).catchError((e) async* {
yield* _handleError(e); //This won't work either
});
Response response = await _repository.getData(event.id); //This do works but I want to use it like above, is it possible?
yield* _processResult(response); //This do works
}
The question is however, how to combine between Future and Stream in dart.
I could use await _repository.getData which works. but then I won't catch the error.
await is just syntactic sugar for .then(), and putting await in a try-catch block is syntactic sugar for using .catchError. Things that you can do one way can be done with the other.
In your first version that uses .then()/.catchError(), your function doesn't return anything.
Your callbacks won't work because you're using yield* in them, but you haven't specified the callbacks with sync* or async*. To avoid name collisions, the yield keyword requires them (in the same way that await requires a function use async or async*).
Here's a version that should work with .then() and .catchError():
Stream<BlocState> _handleEvent(BlocEvent event) async* {
yield* await _repository.getData(event.id).then((response) async* {
yield* _processResult(response);
}).catchError((e) async* {
yield* _handleError(e);
});
}
Note that the callbacks don't need to use yield*; they could just return their Streams directly:
Stream<BlocState> _handleEvent(BlocEvent event) async* {
yield* await _repository.getData(event.id).then((response) {
return _processResult(response);
}).catchError((e) {
return _handleError(e);
});
}
But (as everyone else has noted) using await instead of the Future API simplifies the whole thing (especially since we're already using await anyway):
Stream<BlocState> _handleEvent(BlocEvent event) async* {
try
response = await _repository.getData(event.id);
yield* _processResult(response);
} catch (e) {
yield* _handleError(e);
}
}
See https://dartpad.dartlang.org/fc1ff92e461754bdb35b998e7fbb3406 for a runnable example.
Try using a try-catch block instead. It works for me with await operations.
To handle errors in an async function, use try-catch:
try {
Response response = await _repository.getData(event.id)
} catch (err) {
print('Caught error: $err');
}
Related
hello I want have to run two functions(Function1() and Function2()) and store value of these returns and run third function. But some time according to condition Function1() or Function2() or both not be run.
if(condition1){
await Function1();
}
if(condition2){
await Function2();
}
await Functon3();
I try as above but Function3() run simultaneously with Function1() or with Function2().
My Function1() code looks like following...
Future Function1() async {
apiService
.apiFileUpload()
.then((value) async {
///codes goes here
}).catchError((error) {
print('EEEE: ' + error.toString());
});
}
If anything not clear please let me know in the comment section.
Please do not use .then() in combination with async/await. It is technically possible, but it takes some skill to get it right, so why make it hard on yourself. Stick with one way of doing it, use either one or the other. You mixed it up and through a slight oversight, your Function1 does not actually wait on it's result. It just returns, with the function still running in the then block. So you await it, but that does not help.
Since you are using await already, stick with that and remove .then() from your repertoire for now:
Future Function1() async {
try {
final value = await apiService.apiFileUpload();
///codes goes here
} catch(error) {
print('EEEE: ' + error.toString());
}
}
You can use await
Future Function1() async {
try{
final value = await apiService
.apiFileUpload();
final value2 = await secondFuntion();
///add more and condition on values
} catch(e){
.....
}
}
from your question you need to tell the compiler to stop on particular task with await and avoid using then function it will never stop your compiler
your future fuction:
Future Function1() async {
apiService
.apiFileUpload()
.then((value) async {
///codes goes here
}).catchError((error) {
print('EEEE: ' + error.toString());
});
}
Modified Future func
Future Function1() async {
var result = await apiService.apiFileUpload();
if(result == success){
// code goes here
}else{
//you can show your error here
}
}
I'm migrating a project from Bloc 7.0 to 7.2
I have an issue trying handle the migration of this following Stream since it is calling another Stream within it self :
Stream<CustomerState> _mapUpdateNewsletter({...}) async* {
try {
[...]
yield* _mapGetCustomer(); // Calling another Stream here
Toast.showSuccess(message: successMessage);
} ...
}
Here is what the called Stream used to look like
Stream<CustomerState> _mapGetCustomer() async* {
try {
final customer = await _customerRepository.getCustomer();
yield state.getCustomerSuccess(customer);
} catch (error, stackTrace) {
ApiError.handleApiError(error, stackTrace);
}
}
Here is what I migrated it to :
Future<void> _onGetCustomer(
GetCustomer event, Emitter<CustomerState> emit) async {
try {
final customer = await _customerRepository.getCustomer();
emit(state.getCustomerSuccess(customer));
} catch (error, stackTrace) {
ApiError.handleApiError(error, stackTrace);
}
}
How am I suppose to call it now in Bloc 7.2 ?
Future<void> _onUpdateNewsletter(UpdateNewsletter event, Emitter<CustomerState> emit) async {
try {
...
yield* _onGetCustomer; // How do I call this async future here?
Toast.showSuccess(message: event.successMessage);
} ...
}
in the new version of the bloc, you don't have to write stream functions. you have a function called emit and calling this function and passing the new state is possible from every function in your bloc. so remove yield* and just call _onGetCustomer function and from there emit your new state.
I have a forEachAsync inside an async* Stream and can't yield.
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
try {
yield ProjectLoading(
message: 'Fetching database',
fetchedCount: 0,
totalCount: 1,
);
await forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
//// ERROR - THIS DOES NOT WORK ////
yield (ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
}, maxTasks: 5);
} catch (error, stacktrace) {
yield ProjectFailure(error: error);
}
}
I've tried other means by dispatching the message and converting it to a state but it doesn't work as well. It seems like the whole app is blocked by this await forEachAsync.
I'm using the bloc pattern which reacts to the emited ProjectStates based on the current ProjectSelected event
Your attempt doesn't work because you're using yield in a callback, not in the function that's returning a Stream. That is, you're attempting the equivalent of:
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
...
await forEachAsync(fileModels, helperFunction);
...
}
Future helperFunction(FileEntity fileModel) async {
...
yield ProjectLoadingTick(...);
}
which doesn't make sense.
Since care about forEachAsync's ability to set a maximum limit to the number of outstanding asynchronous operations,
you might be better off using a StreamController that you can manually add events to:
var controller = StreamController<ProjectState>();
// Note that this is not `await`ed.
forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
controller.add(ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
},
maxTasks: 5);
yield* controller.stream;
I recently learned of the fabulous way of waiting for multiple async functions to complete using Future.wait([asyncFuncOne(), asyncFunctwo()])
However, I noticed two different outcomes when running either of these blocks of code. One awaiting each function to finish, the other using Future.wait for parallel processing. What am I doing wrong?
Method 1:
await msm.initProfileData();
await msm.initActivityFeed();
await msm.getRecentlyActiveUsers();
await msm.getRecommendedUsers();
await msm.getGroups();
await msm.getFollowing();
await msm.getFollowers();
Method 2:
await Future.wait([
msm.getFollowing(),
msm.initProfileData(),
msm.initActivityFeed(),
msm.getRecentlyActiveUsers(),
msm.getRecommendedUsers(),
msm.getGroups(),
msm.getFollowers(),
]);
in Method 1, all the async functions complete before my apps home screen appears. In Method 2 the home screen appears before all the async functions complete.
Cheers and thanks in advance.
EDIT: Additional code example.
#override
void initState() {
super.initState();
googleSignIn.onCurrentUserChanged.listen((account) {
handleSignIn(account);
}, onError: (err) {
print('Error signing in: $err');
});
googleSignIn.signInSilently(suppressErrors: false).then((account) {
handleSignIn(account);
}).catchError((err) {
setState(() => _showSignIn = true);
print('Error signing in: $err');
});
}
handleSignIn(GoogleSignInAccount account) async {
if (account != null) {
await createUserInFirestore();
setState(() {
isAuth = true;
});
} else {
setState(() {
isAuth = false;
_showSignIn = true;
});
}
}
createUserInFirestore() async {
final GoogleSignInAccount user = googleSignIn.currentUser;
DocumentSnapshot doc = await usersRef.document(user.id).get();
//...
//do stuff
//...
await someFunc1(); //Method1
// await comeFunc2(); //Method2
//do more stuff
}
someFunc1() async {
msm.asyncfunc1();
msm.asyncfunc2();
}
someFunc2() async {
await Future.wait([
msm.asyncFunc1(),
msm.asyncFunc2(),
]);
}
#override
Widget build(BuildContext context) {
return isAuth ? buildAuthScreen() : buildUnAuthScreen();
}
Using Future.wait(List<Future>) will wait for all the async operations without sequence as mentioned in the docs. While using await consecutively, it'll wait for the first await async operation to finish before running the next await async operation. If you have a prerequisite output before running the next async operation, it's better to use await async in sequence instead.
I would like download the data, but also use the application all the time.
Can you tell me if it's right solution?
The case is we press button download and call funtion bloc.dispatch(Event.download());
In mapEventToState in _Download event we reqest data. But we don't wait for response because we don't want to block others events which are changing view.
So I create Future and after getting response I call event _UpdateData() where I process downloaded data and generate state with them.
It's ok?
There is _requestTime parameter to check if it's last request.
class Bloc {
DateTime _requestTime;
#override
Stream<State> mapEventToState(Event event) async* {
if (event is _Download) {
yield DownloadingState();
_request();
} else if (event is _UpdateData) {
if(!event.requestTime.isBefore(_requestTime))
yield DownladedState(event.response);
}
}
_request() {
_requestTime = DateTime.now();
repository.downloadData().then((response) {
dispatch(_UpdateData(response));
});
}
}
Let me know if it works
Changeadded yield* in front of _request
#override
Stream<State> mapEventToState(Event event) async* {
if (event is _Download) {
yield DownloadingState();
yield* _request();
} else if (event is _UpdateData) {
if(!event.requestTime.isBefore(_requestTime))
yield DownladedState(event.response);
}
}
_request() async*{
_requestTime = DateTime.now();
repository.downloadData().then((response) {
dispatch(_UpdateData(response));
});
}
}