Async request using BLoC in Flutter - 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));
});
}
}

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

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

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.

flutter how to yield to a stream of bloc?

Hi I'm new to flutter and dart. I'm following a lesson on internet which is practicing to use bloc to control states. First lesson is after showing appStart animation, turn to a login page.
the lesson was using 'mapEventToState':
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository? _userRepository;
AuthenticationBloc({UserRepository? userRepository})
: assert(userRepository != null),
_userRepository = userRepository, super(Uninitialized());
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
yield* _mapAppStartedToState();
} else if (event is LoggedIn) {
yield* _mapLoggedInToState();
} else if (event is LoggedOut) {
yield* _mapLoggedOutToState();
}
Stream<AuthenticationState> _mapAppStartedToState() async* {
log('_mapAppStartedToState is running.');
try {
final bool? isSigned = await _userRepository?.isSignedIn();
if (isSigned != null) {
if (isSigned) {
final String? name = await _userRepository?.getUser();
yield Authenticated(name);
}
else {
yield Unauthenticated();
}
}
} catch (_) {
yield Unauthenticated();
}
}
Stream<AuthenticationState> _mapLoggedInToState() async* {
log('_mapLoggedInToState is running.');
yield Authenticated(await _userRepository?.getUser());
}
Stream<AuthenticationState> _mapLoggedOutToState() async* {
log('_mapLoggedOutToState is running.');
yield Unauthenticated();
_userRepository?.signOut();
}
}
turns out 'mapEventToState' was removed.
According to this page(https://github.com/felangel/bloc/issues/2526), I try to use on< event > instead:
#override
AuthenticationBloc({UserRepository? userRepository})
: assert(userRepository != null, 'userRepository == null'),
_userRepository = userRepository,
super(Uninitialized()) {
log('AuthenticationBloc is running.');
on<AppStarted>(_appStarted);
on<LoggedIn>(_loggedIn);
on<LoggedOut>(_loggedOut);
}
Stream<AuthenticationState> _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) async* {
log('_appStarted is running.');
yield* _mapAppStartedToState();
}
But it didn't work. Even log('_appStarted is running.'); didn't show at console.
I tried to change type and aync*. It would show console log if _appStarted isn't aync.
void _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) {
log('_appStarted is running.');
// yield* _mapAppStartedToState();
}
However, it can't yield to stream as _appStarted isn't aync. Makes me confused.
Please let me know if I got some misunderstand about bloc and stream. Happy to see any solution or advise.
You no longer need your one function per event, because you already have it:
void _appStarted(AuthenticationEvent event, Emitter<AuthenticationState> emit) {
log('_appStarted is running.');
try {
final bool? isSigned = await _userRepository?.isSignedIn();
if (isSigned != null) {
if (isSigned) {
final String? name = await _userRepository?.getUser();
emit(Authenticated(name));
}
else {
emit(Unauthenticated());
}
}
} catch (_) {
emit(Unauthenticated());
}
}
If you want to delegate this to another function, just remove the stream return value and pass the emitter.

How can I cancel a 'await for' in a flutter / dart bloc

I am using a stream to read out location data in a bloc. I have a start and a stop event. In the stop method, I cancel the stream subscription. When I use listen to a stream to yield the state the inside where the yield statement is never gets called.
Stream<LocationState> _start() async* {
_locationSubscription = location.onLocationChanged.listen(
(location) async* {
if (location.isNotNull) {
yield LocationState.sendData(location: updateLocation(location));
}
},
);
//send one initial update to change state
yield LocationState.sendData(
location: updateLocation(await Location().getLocation()));
}
Stream<LocationState> _stop() async {
await _locationSubscription?.cancel();
_locationSubscription = null;
yield LocationState.stoped();
}
When I replace the listen to await for I don't see any way
to stop this from yielding events because the subscription handle is gone.
Any ideas? Any explanations?
Stream<LocationState> _start() async* {
await for (LocationData location in location.onLocationChanged) {
if (location.isNotNull) {
yield LocationState.sendData(location: updateLocation(location));
}
}
//send one initial update to change state
yield LocationState.sendData(
location: updateLocation(await Location().getLocation()));
}
The problem is that I did not understand the behavior of yield completely.
Also, the dart framework has some shortcomings.
The problem was discussed in detail with the dart makers, here.
https://github.com/dart-lang/sdk/issues/42717
and here
https://github.com/felangel/bloc/issues/1472

How to use yield to return an error (from catch error)

My service _session try to log in and return true if succeed or an error message from catchError if fail. I would like to yield this message and so, call yield from the catch block, but it's not possible, so I did:
Will this work as I expected or is there another way to do this?
#override
Stream<DgState> mapEventToState(DgEvent event) async* {
if (event is LoginDgEvent) {
yield LoadingState();
String errMessage;
bool hasLogged = await _session
.login(event.userCredential.login, event.userCredential.password)
.catchError((err) {
errMessage = err;
});
yield LoginState(hasLogged ? 'Ok': errMessage);
}
}
You can create a event for update state.
#override
Stream<DgState> mapEventToState(DgEvent event) async* {
//Event for update state
if(event is LoginUpdateStateEvent){
yield event.state;
}
if (event is LoginDolceGustoEvent) {
yield LoadingState();
String errMessage;
bool hasLogged = await _session
.login(event.userCredential.login, event.userCredential.password)
.catchError((err) {
//dispatch
dispatch(LoginUpdateStateEvent(state:LoginErrorState(errMessage)));
});
yield LoginState(hasLogged ? 'Ok': errMessage);
}
}