felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

Question: how to call ancestor bloc from descendant #3174

Closed cdprete closed 2 years ago

cdprete commented 2 years ago

Hi @felangel and thanks for this amazing library :) I've an application which, within the body of a Scaffold, renders a list of items and the FloatingActionButton of such Scaffold is used to open a dialog which the user can use to add a new item. In such scenario, I've at the moment 2 blocs: one for adding the new item and one for listing the available items in a paginated way.

I've the requirement to refresh the list of items as soon as the new item is added, but I cannot imagine how to it with flutter_bloc in a "nice" way.

Since the 2 blocs are under different contexts, I cannot really read the bloc handling the list from the one adding the new item. One way I can imagine is to lift the state of the list of items up, so that the Scaffold is child of the BlocProvider instead of being the latter the child of the body of the former, but it would be really ugly because the Scaffold renders a BottomNavigationBar with 3 items (and therefore 3 pages) and this would mean define a MultiBlocProvider which provides the Bloc to each page. As a result, the bloc managing the list (and, in the future, the ones from the other pages) would be really out of scope in my opinion (in the sense and its scope is way broader than it should be).

What do you suggest from your point of view? How would you design the interaction with the blocs in such scenario?

Kind regards and thanks in advance, Cosimo

marcossevilla commented 2 years ago

hi @cdprete 👋 can you share a minimal reproducible sample of your code? you can use this DartPad as starting code to share the sample 👍

cdprete commented 2 years ago

Hi @marcossevilla. For what I know, it's not possible to import libraries in DartPad. Am I right?

If it's so, I'll prepare instead an example on GitHub that you could check out.

cdprete commented 2 years ago

Hi @marcossevilla. I've added the example code at https://github.com/cdprete/github_flutter_bloc_issue_3174 :)

alexaung commented 2 years ago

@cdprete how do you refresh the list. I also facing the same issue. I create two bloc, list and item. For work around, I merge two to one bloc, and at create new, I call the list state and add the new item and then emit create success and emit list state. For me it is working fine but I am not so sure, emitting two, create success and list loaded state is correct way or not.

cdprete commented 2 years ago

@cdprete how do you refresh the list. I also facing the same issue. I create two bloc, list and item. For work around, I merge two to one bloc, and at create new, I call the list state and add the new item and then emit create success and emit list state. For me it is working fine but I am not so sure, emitting two, create success and list loaded state is correct way or not.

Hi. I didn't find a nice solution yet.

marcossevilla commented 2 years ago

hi @cdprete! I'm really sorry for the delayed response but I got caught up in other issues and work tasks, but I finally raised a PR in your sample repo, I think you should treat your add item functionality as part of your ItemListBloc, that way it's easier to emit state changes based on the addition of a new item.

Hope the PR fixes your issue and if it doesn't, feel free to comment here!

cdprete commented 2 years ago

Hi @marcossevilla. No worries for the delay; we are all busy with our works ;)

Putting the add of an item into the ItemListBloc will, of course, solve the issue (since the bloc is then provided by the parent view), but this would go against reusability, SRP, SoC, packaging-by-feature and many other aspects. From an architectural point of view, I don't really find the solution acceptable since it mixes up two different features together (listing the items and add a single item).

What would happen if we don't dispose a Bloc every time the view is re-created? How is the state maintained? Could there be memory leaks? I'm asking this because, one way, would be to have the Blocs as singletons where one Bloc would be injected into another (like a Service composition from DDD). In such a view, I would see the addition of the flags automaticDispose (so that's disposed by the IoC container, registry or whatever DI mechanism) and emitInitStateOnReRender (to have a pristine state on every re-render, like when it's disposed and recreated) as a potential solution. Of course, emitInitStateOnReRender could not be enough (maybe you need to free some resources at the beginning or so), but it's a starting point ;)

marcossevilla commented 2 years ago

IMO your listing feature is concerned by everything that happens in the list scope, in this case you add a new item to the list, so the list's bloc should be affected by an ItemAdded event.

If you want to keep the two blocs aside, you can add another event to your ItemListBloc to add that new item to that state, but there you would duplicate the add functionality in 2 features instead of treating them as one, does that make sense?

We recommend having one bloc per feature, in this case it should be only one and its single responsibility should be managing list operations (listing, adding, removing, modifying, etc.) and the state of it needs to be persisted to render it on screen.

Hope that helps and let me know if you have more questions. 👍

cdprete commented 2 years ago

IMO your listing feature is concerned by everything that happens in the list scope, in this case you add a new item to the list, so the list's bloc should be affected by an ItemAdded event.

If you want to keep the two blocs aside, you can add another event to your ItemListBloc to add that new item to that state, but there you would duplicate the add functionality in 2 features instead of treating them as one, does that make sense?

We recommend having one bloc per feature, in this case it should be only one and its single responsibility should be managing list operations (listing, adding, removing, modifying, etc.) and the state of it needs to be persisted to render it on screen.

Hope that helps and let me know if you have more questions. 👍

I see your points. I'm not fully convinced, though. The weird thing is that a simple callback approach should be enough (the AddItem view takes a callback to call when the add operation is completed and the body of the callback would refresh the list), but somehow it crashes/has issues with the contexts...

felangel commented 2 years ago

@cdprete I just opened a pull request with my suggestions for how to achieve the desired behavior without tightly coupling the two blocs. Hope that helps 👍

cdprete commented 2 years ago

@felangel thanks for the feedback. I'll have a look at it asap.

felangel commented 2 years ago

@cdprete closing for now but feel free to comment with any additional thoughts/questions and I'm happy to continue the discussion 👍

cdprete commented 2 years ago

@felangel I checked the changes and I cannot see how to achieve pagination with those :-) As stated in the comments in the example, the items are paginated in the real app, therefore listening to the stream is not really good IMO (without considering you're exposing internal details which should be encapsulated).

The flow (idea) is:

  1. You're on page X
  2. An item is added/removed
  3. Fetch again the list (from page 1, since the order within the items maybe is changed)

Please let me know if I'm missing something. I'm not a Flutter expert ;)