Migrate to BLoC 7.2- Nested Streams - yield* inside other stream - flutter

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.

Related

Flutter Bloc cancel running event when a new one is recieved

I have the follwing Bloc
class DeviceBloc extends Bloc<DeviceEvent, DeviceState> {
DataRepository repository;
DeviceBloc({#required this.repository}) : super(DeviceInitialState()) {
on<FetchDevicesEvent>(onFetchResources);
}
Future<void> onFetchResources(
FetchDevicesEvent event, Emitter<DeviceState> emit) async {
emit.call(DeviceLoadingState());
try {
List<DeviceResource> devices = await repository.getResources(event.type);
emit.call(DeviceLoadedState(devices: devices));
} catch (e) {
emit.call(DeviceErrorState(message: e.toString()));
}
}
}
When FetchDevicesEvent event is triggered it starts a long running task, if additional FetchDevicesEvent events are recieved before the running task is completed the wrong result are returned to the caller. How can I suspend the awaited task and always start a new as soon as a new FetchDevicesEvent is recieved?
Found the solution myself by using transformer: restartable() from bloc_concurrency package
class DeviceBloc extends Bloc<DeviceEvent, DeviceState> {
DataRepository repository;
DeviceBloc({#required this.repository}) : super(DeviceInitialState()) {
on<FetchDevicesEvent>(
onFetchResources,
transformer: restartable(),
);
}
Future<void> onFetchResources(
FetchDevicesEvent event, Emitter<DeviceState> emit) async {
emit.call(DeviceLoadingState());
try {
final List<DeviceResource> devices =
await repository.getResources(event.type);
emit.call(DeviceLoadedState(devices: devices));
} catch (e) {
emit.call(DeviceErrorState(message: e.toString()));
}
}
}

Can't yield in forEachAsync inside Stream in dart/flutter

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;

How to work with async* functions in Dart

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

Async request using BLoC in Flutter

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

How to listen on a future stream in Dart?

I am trying to listen to a future stream and I am running into an error. I have tried everything and yet the error returned is;
The method 'listen' isn't defined for the class 'Future'. Try
correcting the name to the name of an existing method, or defining a
method named 'listen'.dart(undefined_method)
How do I accomplish this?
Stream<AdventuresState> _mapLoadAdventureToState() async* {
_adventureSubscription?.cancel();
try {
_adventureSubscription = _adventureRepository.getAdventures(_profileID).listen(
(adventure) => add(
AdventuresUpdated(adventure),
),
);
} catch (error) {
AdventureError("Error: $error");
}
}
Receiving stream events as Future
The asynchronous for loop (commonly just called await for) iterates over the events of a stream like the for loop iterates over an Iterable.
Future<AdventuresState> _getSingleStep(Stream<AdventuresState> adventureStream) async {
await for (var adventure in adventureStream) {
return adventure;
}
}
Source: https://dart.dev/tutorials/language/streams
Is something like this you want to do?
Stream<AdventuresState> _mapLoadAdventureToState() async* {
final adventure = await _adventureRepository.getAdventures(_profileID);
yield AdventuresUpdated(adventure);
}