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

Add the concept of side effects #394

Closed
kaciula opened this issue Jul 4, 2019 · 6 comments
Closed

Add the concept of side effects #394

kaciula opened this issue Jul 4, 2019 · 6 comments
Assignees
Labels
question Further information is requested
Projects

Comments

@kaciula
Copy link

kaciula commented Jul 4, 2019

I like to keep a bloc for every screen and update the widgets when the state changes. However, for one-time things (such as showing snackbar/dialog, moving to another screen) I have to change the state to something simple such as ShowSnackbar and use a BlocListener. I do not want to lose my data from the previous state so I have to pass it along to ShowSnackbar state (or to just have a bool showSnackbar inside my data state and reset the value by sending an extra event after showing the snackbar). This involves a lot of boilerplate code.

Another issue is that if there are 2 consecutive identical ShowSnackbar events, the second one does not go through because of using BlocListener. But I also do not want to use BlocBuilder for this because as I understand it, it may rebuild for other framework reasons.

I come from the Android world and I use a library there called Mobius (https://github.com/spotify/mobius) which you could say is similar to what this library does but it's a bit better IMO. Apart from the concepts of model and event, it has the concept of side effect. So instead of sending just a state, a bloc could send a state and/or a side effect. Side effects are one-time things. Admittedly, in dart you can implement some of the side effects right there inside the bloc using async-await but you cannot implement the UI side effects because of needing a BuildContext.

In Mobius, the equivalent mapEventToState is a pure function: only deciding the state and/or side effects based on the previous state and the event. And the side effects are handed to someone else to deal with them. It's a very elegant and beautiful approach.

In a nutshell, I would like to have something like BlocSideEffectsListener to listen to side effects sent from the bloc, without changing my state. In this context, to me state means the data needed to draw/redraw the UI and side effects mean UI one-time things (fire-and-forget). You can check out the Mobius concepts ( https://github.com/spotify/mobius/wiki/Concepts) to get a feel for how everything is wired.

I don't know if I was clear. I'm new to Flutter and dart and your library helped me get up to speed. Thank you for this.

@felangel
Copy link
Owner

felangel commented Jul 4, 2019

Hi @kaciula 👋
Thanks for opening an issue!

First off, I really appreciate you taking the time to go into detail and providing examples to give more context. Regarding your question, I have tried very hard to stay away from "side-effects" in bloc because I feel it just adds unnecessary complexity. In the case you've described, I would not recommend having ShowSnackbar states etc because the bloc should not be tightly coupled to the UI. Instead, your states should be things like Initial, Loading, Loaded, Failure, etc... and your UI layer (via BlocListener) can handle showing SnackBars or doing other "one-time-actions" in response to the states. I would love to hear your thoughts and thanks again for bringing this up! 👍

@felangel felangel self-assigned this Jul 4, 2019
@felangel felangel added question Further information is requested waiting for response Waiting for follow up labels Jul 4, 2019
@felangel felangel added this to In progress in bloc Jul 4, 2019
@kaciula
Copy link
Author

kaciula commented Jul 5, 2019

Thanks @felangel for answering.

I guess I may be using bloc in a somewhat different manner than it is designed for. Each screen has one bloc that contains the state needed by the screen. And the screen is a "passive view" in the sense that it does not do any logic but renders itself based on the state sent by the bloc and the screen sends all the clicks as events back to the bloc. In essence, the bloc drives the view. I found this approach worked and scaled really well on Android.

Regarding tight UI coupling, the semantics of the events and states can avoid this. Instead of ShowSnackbar you could say ShowError and the UI in response can show snackbar/alertdialog or whatever. The bloc doesn't know or deal with any UI implementation. Some may argue that showing an error is a business decision.

I understand if you don't think a stream of side effects should be implemented in the library. My use cases however definitely need it. Maybe an extension can do the job. I am not yet proficient in dart to attempt this myself but I will be considering it in the future.

Having said all this, how can I make bloc work in my use cases?

  1. I can have a random property for the ShowError/Failure state so that the BlocListener is called every time even if there are "identical" consecutive states.
  2. I can have a property previousState on the ShowError/Failure state so that after showing the error, I can send an event back ShownError and the bloc can extract the previous state and set it as the current state. I don't like this approach because it introduces an extra event for each "one-time action"

So basically I'm trying to make my particular preferred approach work with bloc.

@felangel
Copy link
Owner

felangel commented Jul 5, 2019

Hi @kaciula no problem!

Are you able to share the link to a sample app that I can take a look at and help refactor to use flutter_bloc? I think it would be much easier to look at your specific use-case rather than talk about side-effects at a high level. I don't think options 1 or 2 are good solutions and they should not be necessary to achieve the behavior you want.

I'd be happy to do a live coding session and step through your use-case in more detail if you'd like. If you aren't on the gitter chat you should definitely join and we can continue the discussion there.

Let me know what you think! 👍

@kaciula
Copy link
Author

kaciula commented Jul 5, 2019

@felangel Thank you for the help on the gitter chat.

The conclusion is that I can skip extending Equatable for the "side-effects" and thus the BlocListener is called even for consecutive identical states. And I can use the condition field to avoid unnecessary rebuilds of BlocBuilder when all I want is to have a UI side-effect from the BlocListener.

Still, at a conceptual level, I really like the side-effect aspect found in the Mobius library and the Elm language. And I am looking forward for someone to do a similar implementation in dart ;)

@kaciula kaciula closed this as completed Jul 5, 2019
@felangel
Copy link
Owner

felangel commented Jul 5, 2019

@kaciula no problem! I agree that conceptually it makes sense but I would urge you to try to just think in terms of state changes. Try to think of a bloc as a finite state machine and if you run into cases where you feel like the tools provided are limiting you, then I'm always happy to discuss and see if there's a solution or if we need to enhance the library.

P.S you might want to check out mobx.

Cheers!

@felangel felangel removed the waiting for response Waiting for follow up label Jul 5, 2019
@felangel felangel moved this from In progress to Done in bloc Jul 5, 2019
@karohovhannisyan1992
Copy link

hi @felangel and @kaciula .
Тhank you for your interesting discussion.
@felangel like you, I also came from android :)

maybe you found a good answer to your question?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
bloc
  
Done
Development

No branches or pull requests

3 participants