Flutter Bloc cancel running event when a new one is recieved - flutter

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

Related

Make a get request after the first one is finished Getx

i want make get request after the first one finished because i need to send data from the response of first request with the body of second request to the server .. how i do this with getx
thanks
class ProductsController extends GetxController with StateMixin<Posts> {
#override
void onInit() {
getData();
super.onInit();
}
getData() async {
try {
change(null, status: RxStatus.loading());
await postsApiProvider
.getPosts()
.then((value) {
change(value, status: RxStatus.success());
});
} catch (exception) {
change(null, status: RxStatus.error(exception.toString()));
}
}
// i want this function fire after getData()
_getRelated() async {
try {
await postsApiProvider
.getRelated(
price: value.region ----> because i need access to getDate values
)
.then((value) {
});
} catch (e) {
debugPrint(e.toString());
}
}
}
I tried that method but it didn't work :
#override
void onReady() {
_getRelated();
super.onReady();
}
You can achieve your result by using finally. But I would suggest creating a function for that, so that in future if you want to add one more function based on second function's callback then you can easily achieve it.
Here's the example:
gloablfun()async{
await getData();
await _getRelated();
}
then you can call the global function in the onInit method:
#override
void onInit() {
gloablfun();
super.onInit();
}
By using this way, you can add more functions in the future and your code will look cleaner than before.
try using try with finally e.g
//a sample model which is have data
final Rx<MyModel> model = Model().obs;
#override
void onInit() {
myFunctiontocall();
super.onInit();
}
myFunctiontocall() async{
try{
// from the first function
// make a function that will have data to the model
myFirstFunctionToRun();
}finally{
// from done process in the above next is the finally
// which next to be run
// call the second function but make sure the model
// have data
thenFinallymySecondFunctiontoRun();
}
}

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 bloc migration due to mapEventToState is not working

I am following the migration to the new bloc 8.0.0. I am trying to remove the mapEventToState but I am having a difficulty in doing so. Can you help me how to do it. I have tried it below but it won't work.
Old code:
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState());
SignInState get initialState => SignInInitialState();
#override
Stream<SignInState> mapEventToState(
SignInEvent event,
) async* {
if (event is SignInWithGoogle) {
yield* mapSignInWithGoogleToState();
}
}
Stream<SignInState> mapSignInWithGoogleToState() async* {
yield SignInWithGoogleInProgressState();
try {
String res = await authenticationRepository.signInWithGoogle();
yield SignInWithGoogleCompletedState(res);
} catch (e) {
print(e);
yield SignInWithGoogleFailedState();
}
}
...
Migration code (does not work):
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState())
{
SignInState get initialState => SignInInitialState();
on<SignInWithGoogle>((event, emit) => emit(mapSignInWithGoogleToState()));
}
Stream<SignInState> mapSignInWithGoogleToState() async* {
yield SignInWithGoogleInProgressState();
try {
String res = await authenticationRepository.signInWithGoogle();
yield SignInWithGoogleCompletedState(res);
} catch (e) {
print(e);
yield SignInWithGoogleFailedState();
}
}
...
Your issue is that mapSignInWithGoogleToState() is returning a Stream of States, but the new structure needs explicit invocations of emit each time a new state needs to be emitted.
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc({required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<SignInWithGoogle>(mapSignInWithGoogleToState);
}
Future<void> mapSignInWithGoogleToState(
SignInWithGoogle event,
Emitter<SignInState> emit,
) async {
emit(SignInWithGoogleInProgressState());
try {
String res = await authenticationRepository.signInWithGoogle();
emit(SignInWithGoogleCompletedState(res));
} catch (e) {
print(e);
emit(SignInWithGoogleFailedState());
}
}
}
Here is some more information as to "why?": https://bloclibrary.dev/#/migration?id=rationale-8
The getter does not belong in the constructor body and I guess it isn't really needed anymore. You could solve the problem like this:
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final AuthenticationRepository authenticationRepository;
final UserDataRepository userDataRepository;
SignInBloc({required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<SignInWithGoogle>(_handleSignInWithGoogleEvent);
}
Future<void> _handleSignInWithGoogleEvent(
SignInWithGoogle event,
Emitter<SignInState> emit,
) async {
// TODO do your thing and create and emit the SignInWithGoogleState
emit(SignInWithGoogleState());
}
}
Be careful, the events in bloc v8 are not handled sequentially anymore, but concurrently. Give this blog post a read: https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency

How can I write "Event1 'or' Event2" inside on<Event> method from flutter_bloc?

That's my code for PostsBloc:
class PostsBloc extends Bloc<PostsEvent, PostsState> {
final _dataService = DataService();
// Constructor
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent>((event, emit) async {
emit(LoadingPostsState());
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
}
So, I want to use the same method with new event, just without emitting LoadingPostsState() like this:
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent || PullToRefreshEvent>((event, emit) async {
if(event == LoadPostsEvent){
emit(LoadingPostsState());
}
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
What you want is the is operator:
if (event is LoadPostsEvent)
However you run into another problem:
on<LoadPostsEvent || PullToRefreshEvent>
this is not a thing. I believe you have two options:
Either make a new event X and have LoadPostsEvent and PullToRefreshEvent extend it, like this:
class LoadEvent extends PostsEvent { ... }
class LoadPostsEvent extends LoadEvent { ... }
class PullToRefreshEvent extends LoadEvent { ... }
on<LoadEvent>((event, emit) {
if (event is LoadPostsEvent)
});
or, in order to minimize code repetition, declare this event handler as a function
on<LoadPostsEvent>(_loadEvent);
on<PullToRefreshEvent>(_loadEvent);
...
void _loadEvent(PostsEvent event, Emitter<PostsState> emit) {
...
}

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