Bloc: How can I `yield` a state from the recurring callback of a Timer?

Created on 13 Jun 2020  ·  12Comments  ·  Source: felangel/bloc

I have an uninitialized Timer object in my class and when user hits start button, it is initialized, and inside it's callback I need to call yield ....
I'm new to dart and this is the code I have so far. Where should I modify?

if (event is CounterETimerStart) {
        timer = Timer.periodic(Duration(seconds: 1), (timer) async* {
          yield CounterNewSecond(++m.passedTime);
        });
}
question

Most helpful comment

Totally overlooked the gist link. Thanks for both links, much appreciated.

All 12 comments

Hi @SIMMORSAL 👋

You can add an event and react to it.

if (event is CounterETimerStart) {
        timer = Timer.periodic(Duration(seconds: 1), (timer) {
          add(TimerTicked(payload));
        });
}

if (event is TimerTicked) {
  yield CounterNewSecond(event.payload);
}

Hey @RollyPeres,

Thanks. This is very good, but it just doesn't feel that elegant!
Is it the only way to do this?

I assume you're after something like

 yield* Timer.periodic(Duration(seconds: 1), (timer) async* {
          yield something;
        });

but that's not possible.

Alternatively you could use:

Stream<int> get stream async* {
  yield* Stream.periodic(const Duration(seconds: 1), (index) => index);
}
stream = Stream.periodic(const Duration(seconds: 1),
            (index) => CounterNewSecond(++m.passedTime));
yield* stream;

I wrote this and now CounterNewSecond is yielding, but the bloc now can't listen to any event. I believe I've not written the code correctly.
Could you provide a more complete code, because I don't really know what to do with the code in the alternate way you offered.

If you want the bloc to react to a specific event then you need to add the event to the bloc like:
add(TimerTicked(payload));

If you want to directly yield a state then the alternative suggestion would work.

stream = Stream.periodic(const Duration(seconds: 1),
            (index) => CounterNewSecond(++m.passedTime));
yield* stream;

This assumes CounterNewSecond is a state class and not an event.
You can't yield events from the bloc but only states. Events are added to the bloc in order to be transformed into states.

I realize that. What I'm saying is when I'm listening to start event, I want to keep yielding the CounterNewSecond state, and the code above does that perfectly.

The problem with it is that when the above code starts running, the bloc wont receive any other events that I send to it from the screen code.

You're adding an event per second, there's plenty of time for other events to be processed. The events are processed in the same order they are added, e.g.: CounterNewSecond, SomeEventFromUI, CounterNewSecond.
If you think there's some issues you can't overcome feel free to share a minimal reproduction gist/repo and I can have a look 👍

Imagine this;
There are 3 buttons on screen, btn1, btn2, and btnStart.

The bloc code looks like this:

counter_event.dart:

class CounterEvent {}

class EventBtn1Click extends CounterEvent {}
class EventBtn2Click extends CounterEvent {}
class EventBtnStartClick extends CounterEvent {}

counter_state.dart:

class CounterState {}

class StateOnBtn1Click extends CounterState {}
class StateOnBtn2Click extends CounterState {}
class StateOnBtnStartClick extends CounterState {
  final int s;
  StateOnBtnStartClick(this.s);
}

counter_bloc.dart:

class CounterBloc extends Bloc<CounterEvent, CounterState> {

  final m = ... // a model

@override
Stream<CounterState> mapEventToState(
  CounterEvent event,
) async* {

  print("---  " + event.toString());

  if (event is EventBtn1Click) {
    yield StateOnBtn1Click();
  }
  else if (event is EventBtn2Click) {
    yield StateOnBtn2Click();
  }
  else if (event is EventBtnStartClick) {
    yield* Stream.periodic(const Duration(seconds: 1),
            (index) => StateOnBtnStartClick(++m.passedTime));
  }
}

Somewhere in my UI code, I print all of the states that I receive with this code:

print("+++  " + state.toString());

Now if I click on btn1, then btn2, then btnStart and then repeat and start from btn1..., this is the output I get:

--- EventBtn1Click
+++ StateOnBtn1Click
--- EventBtn2Click
+++ StateOnBtn2Click
--- EventBtnStartClick
+++ StateOnBtnStartClick
+++ StateOnBtnStartClick
+++ StateOnBtnStartClick
+++ StateOnBtnStartClick
+++ StateOnBtnStartClick
...

It seems like when Stream.periodic(...) starts working, the thread is occupied by it and the bloc wont react to any other event coming in.

Your model m should not be on the bloc but on the state itself.
Here's a gist which does what I assume you want.
My bad, the approach was not working because the default bloc event processing operator asyncExpand pauses the event stream in order to process it, event by event and Stream.periodic never completes so other events won't get the chance of being processed, unless for some rx magic. switchMap converts each incoming event into a new Stream that will complete after being processed so now other events will get the chance of being handled.

Hope that solves your issue and sorry for missing this crucial aspect ✌

No problem. Sorry for late replies.

About the model not being on the bloc, why? If bloc is supposed to house all the business logic, and the logic does some work on the model and modifies it and later saves it, wouldn't the best place for it be the bloc? As I understand and read about it, bloc pattern is like MVVM in native android. The ViewModel class holds all the data, and it's then passed down to View so it can show stuff based on it, and sometimes directly access the data through the viewModel object, but the view never directly holds it or does operations on it. Have I understood the BLoC pattern wrong?

And thanks for the explanation, but could I trouble you to use the switchMap in a code snippet? I'm not well versed enough in rx yet!

There's similarities between the two patterns but also differences.
While in MVVM you'd do bindings on view model's props which holds the data, with bloc you'd consume state props since bloc's state is in charge of holding the data and not the bloc itself.
To have a better understand please check out the architecture overview

Here's a gist which does what I assume you want.

My previous reply already contains a link to a complete rework of your code to use switchMap so if you click on the blue 'gist' you'll get to it. 👍

Totally overlooked the gist link. Thanks for both links, much appreciated.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

komapeb picture komapeb  ·  3Comments

clicksocial picture clicksocial  ·  3Comments

krusek picture krusek  ·  3Comments

nhwilly picture nhwilly  ·  3Comments

hivesey picture hivesey  ·  3Comments