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

[Proposal] Bloc and Cubit to extend BlocBase #2235

Closed
felangel opened this issue Mar 15, 2021 · 22 comments · Fixed by #2234
Closed

[Proposal] Bloc and Cubit to extend BlocBase #2235

felangel opened this issue Mar 15, 2021 · 22 comments · Fixed by #2234
Assignees
Labels
breaking change Enhancement candidate would introduce a breaking change discussion Open discussion for a specific topic enhancement candidate Candidate for enhancement but additional research is needed feedback wanted Looking for feedback from the community pkg:bloc This issue is related to the bloc package
Projects
Milestone

Comments

@felangel
Copy link
Owner

felangel commented Mar 15, 2021

Summary

  • Define a common interface for Bloc and Cubit instances called BlocBase rather than having bloc directly extend cubit or vice versa.
  • Include changes in refactor(bloc)!: Bloc and Cubit extend BlocBase #2234 as part of the stable v7.0.0 release of package:bloc (read on for rationale).

Please leave a 👍 if you agree with the proposal or a 👎 if you disagree. If you disagree, it would be great if you could also leave a comment explaining why with any alternative suggestions 🙏

Breaking Changes

BlocObserver

Before
class SimpleBlocObserver extends BlocObserver {
  @override
  void onCreate(Bloc bloc) {...}

  @override
  void onEvent(Bloc bloc, Object? event) {...}

  @override
  void onTransition(Bloc bloc, Transition transition) {...}

  @override
  void onError(Bloc bloc, Object error, StackTrace stackTrace) {...}

  @override
  void onClose(Bloc bloc) {...}
}
After
class SimpleBlocObserver extends BlocObserver {
  @override
  void onCreate(BlocBase bloc) {...}

  @override
  void onEvent(BlocBase bloc, Object? event) {...}

  @override
  void onTransition(BlocBase bloc, Transition transition) {...}

  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {...}

  @override
  void onClose(BlocBase bloc) {...}
}

Bloc and Cubit

Before
myBloc.map(...)
myCubit.map(...)
After
myBloc.stream.map(...)
myCubit.stream.map(...)

Note: listen will still be available as part of both the bloc and cubit public api until v8.0.0

Problem

As a developer, the relationship between blocs and cubits is a bit awkward. When cubit was first introduced it began as the base class for blocs which made sense because it had a subset of the functionality and blocs would just extend Cubit and define additional APIs. This came with a few drawbacks:

Later, we experimented with inverting the relationship and making bloc the base class which partially resolved the first bullet above but introduced other issues:

Proposal

One way to address these issues is to introduce a base class for both Bloc and Cubit so that upstream components like BlocBuilder, BlocProvider, etc. can interoperate with both bloc and cubit instances.

BlocBase Public API

An interface for the core functionality implemented by both Bloc and Cubit.

abstract class BlocBase<State> {
  State get state;

  Stream<State> get stream;

  StreamSubscription<State> listen(
    void Function(State)? onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  });

  void emit(State state);

  void addError(Object error, [StackTrace? stackTrace]);

  void onError(Object error, StackTrace stackTrace);

  Future<void> close();
}

This would allow for both bloc and cubit to implement this BlocBase API and still maintain specialized public APIs (mapEventToState, add, etc.)

abstract class Bloc<Event, State> implements BlocBase<State> {...}
abstract class Cubit<State> implements BlocBase<State> {...}

This change would also allow us to expose a getter to access the Stream<State> as opposed to having bloc and cubit implement Stream<State> which would address #1942 & #1429 and greatly simplify the public API of both bloc and cubit.

@felangel felangel added discussion Open discussion for a specific topic enhancement candidate Candidate for enhancement but additional research is needed feedback wanted Looking for feedback from the community labels Mar 15, 2021
@felangel felangel added this to To do in bloc via automation Mar 15, 2021
@felangel felangel added breaking change Enhancement candidate would introduce a breaking change pkg:bloc This issue is related to the bloc package labels Mar 15, 2021
@felangel felangel self-assigned this Mar 15, 2021
@felangel felangel pinned this issue Mar 15, 2021
@Gene-Dana
Copy link
Collaborator

Gene-Dana commented Mar 15, 2021

Well.. I like the idea of greatly simplifying the API and I like the reminder that we are working with streams when we are working with Bloc. Seems like a pretty elegant solution ! Are there any downsides, besides a refactor?

@felangel
Copy link
Owner Author

Well.. I like the idea of greatly simplifying the API and I like the reminder that we are working with streams when we are working with Bloc. Seems like a pretty elegant solution ! Are there any downsides, besides a refactor?

In my opinion, the main downside is the breaking change for the rename + the introduction of a BlocStream class. I'm also open to alternative names since BlocStream might not be the best name for this base class.

@jeroen-meijer
Copy link
Collaborator

I think this is a great approach. From the looks of it, I don't see any downsides besides a small refactor.
It would be nice to have a different name than BlocStream since it decouples the implementations (Bloc and Cubit) from the base, but on the other hand, a completely new name might be confusing.

LGTM overall. :shipit:

@wujek-srujek
Copy link

I like the idea 👍 and have a few comments:

  • How about StateStream instead of BlocStream? Essentially, Bloc and Cubit have that in common - they expose a stream of state changes. (Disclaimer: I'm not good with names.)
  • Current parameter naming now has void onCreate(BlocStream bloc) {...}, how about void onCreate(BlocStream blocStream) {...} (or stateStream, see above).
  • Does BlocStream need to also have the Event type? It is meaningless for a Cubit, but it will be engraved in its API forever (until the next refactoring ;)). If this is changed, Transition<Event, State> would also need to change, but is it necessary for onTransition to be on the base superclass or could Bloc and Cubit define their own?
  • Love the idea to not implement Stream and instead expose a getter. I did this in my own Bloc implementation and didn't notice any downsides.

@Jomik
Copy link
Contributor

Jomik commented Mar 15, 2021

I do not like this idea. Three hours of talk with @felangel convinced me otherwise.
I do not feel like it makes sense to have a notion of Transition outside of the Bloc class, Cubit should not have to care, unless you define a Transition as a State -> State relation, without an Event.
With this proposal, we need to say that Cubit has a Null event type, ew.

To me BlocStream looks like a Cubit with an extra generic for the onTransition support. What logic would the BlocStream have and what would the Cubit extending BlocStream have?

I'd propose keeping Cubit as the base class.
Replies to the problems with this approach:

All APIs would either have to be renamed to accept a cubit for accuracy or they would need to be kept as bloc for consistency even though hierarchically it is inaccurate (#1708, #1560).

I don't believe so. Bloc is the name of the package. So calling it BlocXYZ is fine.
Besides, we are moving towards using context.select instead.

Cubit would need to extend Stream and implement EventSink in order to have a common base which widgets like BlocBuilder, BlocListener, etc. can be implemented against (#1429).

Why? Cubit can expose the state stream the same way that BlocStream in this proposal does.

@felangel
Copy link
Owner Author

@wujek-srujek thanks for the feedback!

How about StateStream instead of BlocStream? Essentially, Bloc and Cubit have that in common - they expose a stream of state changes. (Disclaimer: I'm not good with names.)

Yeah that works. I'm not good with names either haha.

Current parameter naming now has void onCreate(BlocStream bloc) {...}, how about void onCreate(BlocStream blocStream) {...} (or stateStream, see above).

Yeah good call we can rename for consistency 👍

Does BlocStream need to also have the Event type? It is meaningless for a Cubit, but it will be engraved in its API forever (until the next refactoring ;)). If this is changed, Transition<Event, State> would also need to change, but is it necessary for onTransition to be on the base superclass or could Bloc and Cubit define their own?

Yeah I went back and forth on this as well. Cubits still technically have transitions but they just don't include event information. We could remove Event from the BlocStream which would result in defining onTransition separately in the Bloc and Cubit classes which makes it possible for the APIs to be out of sync. I'll think about it some more 🤔

@felangel
Copy link
Owner Author

felangel commented Mar 15, 2021

@Jomik thanks for the feedback!

Just to clarify you're proposing the following:

  • Bloc extends Cubit
  • Neither extends/implements Stream/Sink and instead exposes a Stream<State> getter
  • Keep the naming bloc everywhere even though the type would be Cubit

I do not feel like it makes sense to have a notion of Transition outside of the Bloc class, Cubit should not have to care, unless you define a Transition as a State -> State relation, without an Event.

We would still need to have a concept of Transitions for Cubit as well (previously called Change). Are you suggesting to go back to having two types: class Change<S> and class Transition<E, S> extends Change<S>?

Thanks for the feedback! I really appreciate it 💯

@Jomik
Copy link
Contributor

Jomik commented Mar 15, 2021

Correct.

  • Keep the naming bloc everywhere even though the type would be Cubit.

I honestly do not think it matters so much. I'd much rather keep the name bloc than having to replace it in large projects.
I do not think we really risk a misunderstanding, when keeping it, but changing it will influence a lot of tutorial's out there when pasted into a new project.
Could also argue that you should probably name it blocStream and not cubit or bloc with this proposal.
For the heck of it, you can also keep both named arguments Bloc bloc and Cubit cubit, but have an assert that checks that you only use one 😅

  • class Transition<E, S> extends Change<S>

I did not think of it, but like this! It allows for some nice default stringification for both cases, if we just need to log our transitions.
And it is still easy enough to map it to a Transition with an instanceof check.

@felangel
Copy link
Owner Author

Keep the naming bloc everywhere even though the type would be Cubit.

We could also use a more neutral name like: value which is something I considered. Will need to think some more about this 👍

@wujek-srujek
Copy link

Just some prototyping, I'm not sure if this generics-galore is worth it at all, bit it is possible to define onTransition on StateStream. Not sure how much it impacts other code and testing helpers, though.

class Transition<State> {
  final State previous;
  final State next;

  const Transition(this.previous, this.next);
}

class BlocTransition<Event, State> extends Transition<State> {
  final Event event;

  const BlocTransition(State previous, State next, this.event): super(previous, next);
}

abstract class StateStream<State, T extends Transition<State>> {
  void onTransition(T transition);
}

class Bloc<Event, State> extends StateStream<State, BlocTransition<Event, State>> {
  @override
  void onTransition(BlocTransition<Event, State> transition) {}
}

class Cubit<State> extends StateStream<State, Transition<State>> {
  @override
  void onTransition(Transition<State> transition) {}
}

I personally rally don't like the idea of Cubit bloc parameters, this is very confusing and just... feels wrong. Anyway, just my 2c.

@felangel
Copy link
Owner Author

felangel commented Mar 15, 2021

@wujek-srujek in v6.0.0 Transition is called Change and BlocTransition is the Transition. The main issue with that approach as you mentioned was the Cubit bloc parameters which I want to avoid as well due to Cubit being the base class 👍

@fabriziocacicia
Copy link

Just a thought on the naming.
I agree that BlocStream is not the best name. Don’t forget that bloc come from ‘business logic component’, so BlocStream doesn’t make so much sense to me.
Instead, I like StateStream, as @wujek-srujek wrote, since both Bloc and Cubit are actually used as stream of states.

@felangel
Copy link
Owner Author

@fabriziocacicia @wujek-srujek another option that @Jomik brought up is BlocBase which might be better since the class is the base class for the bloc package. Thoughts?

@wujek-srujek
Copy link

BlocBase instead of BlocStream? Fine by me.

@felangel felangel changed the title [Proposal] Bloc and Cubit to extend BlocStream [Proposal] Bloc and Cubit to extend BlocBase Mar 15, 2021
@felangel felangel added this to the v7.0.0 milestone Mar 15, 2021
@gondaimgano
Copy link

the traditional bloc works fine for me unless there is some added benefit I am not seeing from this proposal.

@felangel
Copy link
Owner Author

@gondaimgano thanks for the feedback! To be clear, this proposal is to simplify the existing public API surface area of bloc and cubit and to make the relationship between bloc and cubit more natural. As a developer, you will still be able to use bloc and cubit as you normally do with a few minor breaking changes (mentioned above).

@narcodico
Copy link
Contributor

I hate to be the one to say it, but this looks more and more like a StreamController to me 😀

@Gene-Dana
Copy link
Collaborator

Guys, BlocStream is a pretty good name. What else could it be? BlocCurrent? BlocFlow? BlocPower? BlocWire? BlocWave? BlocVacuum? BlocRiver xD?

@Gene-Dana
Copy link
Collaborator

BlocBridge, BlocTx, BlocPipe?

@Gene-Dana
Copy link
Collaborator

I think BlocStream is pretty good name :D

@wujek-srujek
Copy link

@Gene-Dana Understood ;d

@felangel
Copy link
Owner Author

Done as part of #2234 🎉

bloc automation moved this from To do to Done Mar 17, 2021
@felangel felangel linked a pull request Mar 17, 2021 that will close this issue
7 tasks
@felangel felangel unpinned this issue Mar 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change Enhancement candidate would introduce a breaking change discussion Open discussion for a specific topic enhancement candidate Candidate for enhancement but additional research is needed feedback wanted Looking for feedback from the community pkg:bloc This issue is related to the bloc package
Projects
bloc
  
Done
Development

Successfully merging a pull request may close this issue.

8 participants