Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit events from blocs #259

Closed
janosroden opened this issue May 3, 2019 · 19 comments
Closed

Emit events from blocs #259

janosroden opened this issue May 3, 2019 · 19 comments
Assignees
Labels
enhancement New feature or request

Comments

@janosroden
Copy link

Hi,

I thinking about a general solution to emit events from blocs.

The concrete use case where I need it is a UserBloc where I want to emit Login/Logout events at first. UserBloc processes UpdateProfileEvents which results in a changed LoggedInUserState, so getting LoggedInUserState from the stream doesn't mean the user just logged in.
For analytics purposes I want to catch a special transition (see below)
where a guest user became a non-guest user.

This is the current implementation placed in the UserBloc. With a little work this can be refactored out of this class, but it still would be ugly.

  @override
  void onTransition(Transition<Event, State> transition) {
    super.onTransition(transition);
    State currentState = transition.currentState;

    if (currentState is GuestUserState && transition.event is LoginEvent) {
      state.skip(1).firstWhere((s) => s is! InProgressState).then((s) {
        if (s is LoggedInUserState)
          app.analytics?.event("login", parameters: {"u": s.user.userId});
      });
    }

    if (currentState is LoggedInUserState && transition.event is LogoutEvent) {
      app.analytics
          ?.event("logout", parameters: {"u": currentState.user.userId});
    }
  }

And this is what I try to achieve:

UserBloc.instance.events.listen((Event event) {
  if (event is LoggedInEvent)
    // do it
  else if (event is LoggedOutEvent)
    // do it
});

I admit that this is not so common to add it in the Bloc class so I could imagine a mixin for this:

mixin EventProviderBloc<TInEvent, TOutEvent, TState> on Bloc<TInEvent, TState> {
  // on Bloc because of dispose()

  // Stream<TOutEvent> get events => ...
  // some @protected helper like dispatch()
  // could provide implementation for onTransition/onError e.g. emit a TransitionEvent
}

Okay, emitting a TransitionEvent is tricky because of TOutEvent, but something similar would be nice. E.g. a TransitionEvent factory hook like TOutEvent _newTranstionEvent(Transition t). I know, implement a hook and demand another makes no sense, but I hope you'll have a better idea, or just leave that :)

So what do you think?

ps: one little annoying thing about the BlocDelegate: please pass the bloc as param

@felangel
Copy link
Owner

felangel commented May 3, 2019

Hi @janosroden 👋
Thanks for opening this issue!

I saw your PR and based on your ask what are your thoughts on blocs having an events stream that you can listen to instead of an EventProvider?

I'm thinking the api would mimic the state api so you'd be able to do something like:

userBloc.events.listen((Event event) {
   // Do stuff
});

One thing to consider is if you override transform and filter events I'm assuming you'd want the filtered events in the events stream as well.

Let me know what you think and thanks again! 👍

@felangel felangel self-assigned this May 3, 2019
@felangel felangel added enhancement candidate Candidate for enhancement but additional research is needed feedback wanted Looking for feedback from the community labels May 3, 2019
@janosroden
Copy link
Author

Adding the event stream to the Bloc class was my first tought, but I think it'd increase both instantiation time and memory consumption. This is a little bit high price for an optional feature.
Implemented as a mixin developers can decide whether they pay this price. This optional functionality also fits very well to the purpose of mixins.

I don't think the transform() is important here, these BlocEvents are responses (mostly) to the already possibly filtered Input Events, so I don't see any reason for more filtering.

Do you agree?

@felangel
Copy link
Owner

felangel commented May 3, 2019

@janosroden I don't think it would increase instantiation or memory consumption because the stream already exists, it just isn't exposed publicly.

I agree about not needing additional filtering. 👍

@janosroden
Copy link
Author

Stream already exists? I don't get how you mean. Can you elaborate?

I'm meant the extra overhead is appear if we put the new StreamController into the widely used Bloc class. People upgrades, and they get an extra controller which is unused (it's new) but still have to be initialized and disposed. In addition I think most of the existing bloc implementations don't need this feature (otherwise it would have been requested already).

Developers who want to use this feature have to change their code anyway both for class or mixin implementation.

@felangel
Copy link
Owner

felangel commented May 3, 2019

Every time you dispatch an event it is added to the private event subject. Currently, the bloc is the only subscriber to the event stream but we can expose it publicly so that anyone can subscribe. Let me discuss this with the team but my initial thought is it would be relatively straightforward to expose this and there would be no breaking change or additional overhead introduced.

@janosroden
Copy link
Author

Ah, I see the misunderstanding: if you check my PR carefully, you'll see I created a second event stream for a possibly different event hierarcy.
What you suggest is that the Event hierarcies have a common base and the bloc should simply ignore its "own" events.

Your idea is actually ... better :)
I don't see any drawbacks except a little bit wierd to call/read dispatch() instead of emitEvent()

@felangel
Copy link
Owner

felangel commented May 3, 2019

Yeah I don't think we need to create additional streams.

One outstanding question I have is what's the use case for having to listen for events when you are in full control over when they are dispatched? Can't you just move whatever logic you have in response to an event before the dispatch? You can dispatch from within the bloc and expose a public wrapper around the dispatch so that from the UI you call bloc.loginPressed()

and in the bloc you have

void loginPressed() {
  if (currentState is ...) {
     // peg analytic here
  }
  dispatch(LoginPressed());
}

Or am I misunderstanding the use-case?

In addition, I agree that including the affected bloc in the onTransition in the delegate makes sense so I will discuss adding it in the next release with the team 👍

@janosroden
Copy link
Author

janosroden commented May 3, 2019 via email

@felangel
Copy link
Owner

felangel commented May 3, 2019

@janosroden makes sense. I was just curious haha. I will try to get this as well as include the bloc in the BlocDelegate onTransition and onError by this evening. 👍

@janosroden
Copy link
Author

janosroden commented May 3, 2019 via email

@felangel
Copy link
Owner

felangel commented May 3, 2019

No problem!

Yup, it will be a breaking change.

Oh so you want onEvent added to BlocDelegate?
What's a case where an event is dispatched without a state change?

@janosroden
Copy link
Author

janosroden commented May 3, 2019 via email

@felangel
Copy link
Owner

felangel commented May 3, 2019

Can you please explain a bit more? I'm not sure I understand. If you dispatch LoggedInEvent won't there always be a state change associated with it?

@janosroden
Copy link
Author

janosroden commented May 3, 2019 via email

@janosroden
Copy link
Author

janosroden commented May 3, 2019 via email

@janosroden
Copy link
Author

Ok, Desktop Site helped

@felangel
Copy link
Owner

felangel commented May 3, 2019

hmm okay I think I understand now. Thanks for clarifying 👍

@felangel
Copy link
Owner

felangel commented May 4, 2019

Merged in #263, #267, and #268 and will be included in bloc v0.13.0 🎉

@felangel felangel closed this as completed May 4, 2019
@felangel felangel removed the enhancement candidate Candidate for enhancement but additional research is needed label May 4, 2019
@felangel felangel added enhancement New feature or request and removed feedback wanted Looking for feedback from the community in progress labels May 4, 2019
@janosroden
Copy link
Author

Big thanks for the quick solution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants