Dart/Flutter Yield state after timer - flutter

I am using flutter_bloc and I am not sure how to yield state from within a callback.
Am trying to start timer and return a new state after a pause. Reason I am using a timer is to get the ability to cancel previous timer so it always returns a new state after an idle state.
#override
Stream<VerseState> mapEventToState(
VerseEvent event,
) async* {
if (event is ABCEvent) {
Timer(const Duration(seconds: 3), () {
print("Here");
_onTimerEnd(); // adding yield here returns an error.
})
}
Stream<XYZState> _onTimerEnd() async* {
print("Yielding a state");
yield myNewState();
}
I can see that the code is getting inside the timer callback as I can see the print statements in the callback but not in the timerEnd() method.

State should be yielded by the stream used in bloc that you are current working on. Like
mapEventToState

Related

'emit was called after an event handler completed normally' issue inside a periodic Timer

Im using Flutter and flutter_bloc to make an app where it periodically sends an API request to my server and requests data.
It was working perfectly at first before i implemented the periodic functionality using Timer, this is my bloc file:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
on<LoadBeehives>((event, emit) => _onBeehivesLoaded(emit));
}
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
emit(BeehivesLoading());
final repository = BeehivesRepository();
const duration = Duration(seconds: 5);
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
emit(BeehivesLoadedSuccessfully(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}
But im getting this exception:
'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted':
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
**BAD**
on<Event>((event, emit) {
future.whenComplete(() => emit(...));
});
**GOOD**
on<Event>((event, emit) async {
await future.whenComplete(() => emit(...));
});
Ive tried searching everywhere for a solution, but honestly this is the first time i used the new version of the bloc package (never used emit before), and i would like some suggestion as to how to solve this issue.
Side question: Is it a good idea to implement the periodic timer there ? because i have seen some people implement it within the frontend (Stateful widget for example), i would also love any suggestions about this.
Thank you very much!
Your code does not wait for the callback inside Timer.periodic to complete - the _onBeehivesLoaded method finishes executing, hence when the callback tries to emit a new state (BeehivesLoadedSuccessfully), you get this error.
To resolve this, instead of emitting a new state inside the callback, you should add a new event to the BLoC and handle it later as any other BLoC event.
First of all, create a new event, like HomeBeehivesLoaded:
class HomeBeehivesLoaded extends HomeBeehivesEvent {
final List<Beehive> beehives; // <-- Not sure if Beehive is the right type, just an assumption
const HomeBeehivesLoaded(this.beehives);
}
Register a new event handler in your BLoC that will update the state based on the loaded behives:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
HomeBeehivesBloc() : super(BeehivesInitial()) {
<...>
on<HomeBeehivesLoaded>(_onHomeBeehivesLoaded);
}
void _onHomeBeehivesLoaded(HomeBeehivesLoaded event, Emitter<HomeBeehivesState> emit) {
emit(BeehivesLoadedSuccessfully(beehives: event.beehives));
}
<...>
}
Inside the Timer.periodic callback, add this event once you get the response from the repository instead of emitting a state:
class HomeBeehivesBloc extends Bloc<HomeBeehivesEvent, HomeBeehivesState> {
<...>
Future<void> _onBeehivesLoaded(Emitter<HomeBeehivesState> emit) async {
<...>
Timer.periodic(duration, (timer) async {
try {
await repository.getHomeBeehives().then((beehives) async {
add(HomeBeehivesLoaded(beehives: beehives));
});
} catch (exception) {
log(exception.toString());
}
});
}
}

How to generate stream in flutter

I am making some experiment with flutter stream. I have a class for generating stream of int. Here is the class :
class CounterRepository {
int _counter = 123;
void increment() {
_counter++;
}
void decrement() {
_counter--;
}
Stream<int> watchCounter() async* {
yield _counter;
}
}
I expect with the change of _counter, watchCounter() will yield updated counter value. When I call increment() or decrement() from UI, it seems the value of _counter is changing but watchCounter doesn't yield the updated _counter value. How to yield updated _counter value here? I am using StreamBuilder from UI to get the streamed data.
You have created your streams using -
Stream<int> watchCounter() async* {
yield _counter;
}
But to reflect the changes of your stream, you need to receive those stream events. You can control those stream events using a StreamController
Creating a stream
Future<void> main() async {
var stream = watchCounter();
}
Using that stream
stream.listen
Subscribes to the stream by calling the listen function and supplys it
with a Function to call back to when there's a new value available.
stream.listen((value) {
print('Value from controller: $value');
});
There are many other approaches to control and manage streams but for your particular question .listen will do the job.
You are missing an infinite while loop, it's a manual stream.
Stream<dynamic> watchCounter() async* {
while (true) {
// you can change it to 5 seconds or higher
await Future.delayed(const Duration(seconds: 1));
yield _counter;
}
}
And after that you just need to call listen:
watchCounter().listen((value) {
// hear can be empty if you want.
});
You can put it in your init state to run, don't put it in your widget build, please.
This should work fine

BlocBuilder builder function gets called only once

Good evening, I have been working with bloc pattern and I have some issues: The state update is being called only once on BlocBuilder, no matter what I do
What I have as state is:
class DateScreenState {
Future<List<PrimaryPetModifierModel>> primaryPetModifiers;
Future<List<SecondaryPetModifierModel>> secondaryPetModifiers;
PrimaryPetModifierModel primaryPetModifierSelected;
SecondaryPetModifierModel secondaryPetModifierSelected;
Widget animatedWidget;
DateTime dateOfSchedule;
DateTime timeOfSchedule;
bool shouldReload = false;
bool isFirstCallAnimation = true;
}
And my mapEventToState looks like this:
#override
Stream<DateScreenState> mapEventToState(
ScheduleDateScreenEvent event) async* {
if (event is SelectPrimaryModifierEvent) {
state.primaryPetModifierSelected = event.modifier;
yield state;
} else if (event is SelectSecondaryModifierEvent) {
state.secondaryPetModifierSelected = event.modifier;
yield state;
}
}
My exact issue is, when I change a value in a DropdownButton, it will fire a SelectedPrimaryModifierEvent or SelectedSecondaryModifier event, the event firing works fine, but the state yielding and updating will happen only once after the first fire of any of those events, after that, BlocBuilder builder function will not be called anymore after any event.
You're yielding the same state with each event, despite the fact that you're changing a variable with the DateScreenState class. Try splitting up your primary and secondary into different state classes and yield them separately in your mapEventToState.

Flutteronic method of scheduling code to run on the next event loop

This is common in other languages. setTimeout(fn, 0) in JavaScript, and DispatchQueue.main.async() {} in Swift.
How best to do this in Flutter?
I have used Future.delayed(Duration.zero).then(fn), but I don't like it because like JS's setTimeout and unlike swifts DispatchQueue.main.async() {} it doesn't really express the intent, only the behaviour. Is there a way of doing this that is the correct way to do this in Flutter.
Use addPostFrameCallback
WidgetsBinding.instance
.addPostFrameCallback((timestamp) {
print("I'm running after the frame was built");
});
This will cause your callback function to run right after flutter has finished building the current frame.
Note that the callback will only run once, if you want to reschedule it for each build, set the callback at the beginning of the build function.
#override
Widget build(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((timestamp) {
print("I'm running after the frame was built");
});
return Container();
}
You can also use Timer from flutter.
Example
Timer(Duration(seconds: 1), () {
print('hai');
});
Duration gives you options with seconds,milliseconds,days,hours,minutes.
You can achieve setInterval also using Timer
Timer.periodic(Duration(seconds: 1), (Timer timer) {
print('hai');
});
But keep in mind that to cancel the timer on dispose.This would save you from hitting memory
Timer timer;
timer = Timer(Duration(seconds: 1), () {
print('hai');
});
void dispose() {
timer.cancel();
}

yield BLoC state inside a timer's recurring callback

How can I yield a state from the recurring callback of a Timer?
I have a Timer object in my class and when user hits start, a new object of it is created, and inside it's callback I need to call yield ....
This is the code I have so far, but it's not doing anything:
if (event is CounterETimerStart) {
timer = Timer.periodic(Duration(seconds: 1), (timer) async* {
yield CounterNewSecond(++m.passedTime);
});
}
With help from #RollyPeres from github, this is one approach:
You can add an event and react to it.
if (event is CounterETimerStart) {
timer = Timer.periodic(Duration(seconds: 1), (timer) {
add(TimerTicked());
});
}
if (event is TimerTicked) {
yield CounterNewSecond(++m.passedTime);
}