Hi Felix, I would be interested in your opinion about adding a custom field/getter to a BLoC, which can also change its value as the BLoC changes states (i.e. it is not set once upon construction). It would also be something visible to the clients of the BLoC, and they would actually need to use it. For example, code within the BlocBuilder.builder callback would use the state, but also look up the BLoC itself and fetch the value of that custom field/getter.
This is not a hypothetical situation, we have that in our code, and it seems wrong, but I couldn't find anything in the documentation that would say that is is wrong, and why. My thoughts: this custom field introduces some kind of a 'parallel state' concept, and it introduces tighter coupling to the BLoC. It makes the BLoC interface more complex, it is not a simple 'event stream input, state output'; it now has an additional field that all clients must use to get complete data. It feels 'dirty'.
What is your opinion?
That sounds like a no-no. I would not want to render my UI based on anything _other_ than the state.
It also sounds harder to test. You have a tight coupling, as you say, so you are unable to remove the Bloc from the widget, which means you _have_ to provide a mocked version of the bloc to test your widget.
Why is this not just part of your state?
Hi @wujek-srujek 馃憢
Thanks for opening an issue!
Yeah I agree that you should generally not maintain parallel state in a bloc. Instead, you should prefer to keep all state as part of the state object itself to keep the bloc interface simple and predictable 馃憤
Thanks for confirming my thoughts, guys.
Some background which hopefully will explain where my doubts are coming from: I am relatively new in the team, and we are working on a new feature, and the team worked on another one previously. What they did was create what I call a MEGA-BLoC: there was just one BLoC for multiple screens, with very complex and convoluted state transitions (the screens didn't have much in common). So, instead of creating new states with data from previous ones (because it got to a point where it was really painful), they just kept a 'bag' of all possible data, all nullable, and the states were pretty much empty types. Whenever a state change occurred, BlocBuilder would get that, but it also needed to get the BLoC itself to invoke the getter for the 'data bag' to fetch its fields. It is a maintenance nightmare, I haven't seen so many null checks before, and there still are problems.
I questioned a lot of things, made enemies along the way, but was convincing enough that I am allowed to try a new approach. So for this new feature we will have multiple smaller BLoCs, some of which will communicate with each other and pass partial data in events and states (this question was about the possible approaches: https://github.com/felangel/bloc/issues/1475). We have been prototyping, and it looks very promising: the BLoCs are very simple, trivial almost, with just a few states (not 30 or more, with strange transitions), very simple to test (don't need to mock the whole world to test everything, just imagine how many dependencies a BLoC for multiple loosely related screens has), plain-vanilla (no custom fields to get data from) and it all seems to be working fine.
@Jomik How would I test a widget that uses a BlocBuilder without mocking a BLoC (or using a real one) and providing it in the context?
@wujek-srujek That sounds very much like a Redux thing to do. You definitely do not want that in BLoC..
Awesome that you are able to try that approach, I am sure you will be happy for it. You get nice separation of concerns, and the BLoCs you create will be easier to test, as they are smaller.
To test a widget. I would split it into two..
One that you can test MyWidgetBody, and one that is a simple mapping of the bloc state, MyWidget, so you should not need to test it.
You can choose to take callbacks for the various events that your widget may dispatch as an argument to the *Body as well, and then map those to BlocProvider<...>.of(...).add(...) calls in the bloc builder widget.
Does that make sense?
class MyWidgetBody extends StatelessWidget {
MyWidgetBody(...);
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder(...);
}
}
Perhaps @felangel has an example somewhere?
I鈥檓 currently working on rewriting most of the examples and they will all be fully tested so stay tuned haha 馃槉
Most helpful comment
Thanks for confirming my thoughts, guys.
Some background which hopefully will explain where my doubts are coming from: I am relatively new in the team, and we are working on a new feature, and the team worked on another one previously. What they did was create what I call a MEGA-BLoC: there was just one BLoC for multiple screens, with very complex and convoluted state transitions (the screens didn't have much in common). So, instead of creating new states with data from previous ones (because it got to a point where it was really painful), they just kept a 'bag' of all possible data, all nullable, and the states were pretty much empty types. Whenever a state change occurred,
BlocBuilderwould get that, but it also needed to get the BLoC itself to invoke the getter for the 'data bag' to fetch its fields. It is a maintenance nightmare, I haven't seen so many null checks before, and there still are problems.I questioned a lot of things, made enemies along the way, but was convincing enough that I am allowed to try a new approach. So for this new feature we will have multiple smaller BLoCs, some of which will communicate with each other and pass partial data in events and states (this question was about the possible approaches: https://github.com/felangel/bloc/issues/1475). We have been prototyping, and it looks very promising: the BLoCs are very simple, trivial almost, with just a few states (not 30 or more, with strange transitions), very simple to test (don't need to mock the whole world to test everything, just imagine how many dependencies a BLoC for multiple loosely related screens has), plain-vanilla (no custom fields to get data from) and it all seems to be working fine.
@Jomik How would I test a widget that uses a
BlocBuilderwithout mocking a BLoC (or using a real one) and providing it in the context?