felangel / bloc

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

Question: Cubit vs Bloc #1444

Closed Peng-Qian closed 4 years ago

Peng-Qian commented 4 years ago

Hi @felangel ,

So excited the cubit out, the method-driven feature is really awesome.

I have some questions about the cubit, could you please explain more about the purpose to create cubit, and what is the key difference/Strength compared to the bloc?

  1. is cubit just designed to offer the syntax sugar or reduce the code size/complexity?
  2. does cubit be capable to do all things that bloc can do? (based on my current understanding and usage, I think it does. But I am not so sure.)
  3. could you give some suggestions/scenarios that should apply cubit rather than bloc?
  4. If the cubit is functionally the same to the bloc and just optimized/simplified structure of bloc, will this be a trend of bloc further development? (should we more prefer to use cubit rather than bloc since it is a kind of evolve of bloc)
felangel commented 4 years ago

Hi @Peng-Qian 👋 Thanks for opening an issue!

  1. Cubit is a subset of Bloc so it reduces complexity but you also lose some functionality.
  2. No, since Cubit has no notion of events, things like debounce, switchMap, throttle, etc... will not be as easy to support with Cubit and you'd be better off using Bloc.
  3. I will cover this in the upcoming documentation updates so stay tuned 😄 but if you're not sure you can start with Cubit and convert your Cubit to Bloc as needed in v6.0.0.
  4. Like I mentioned, Cubit is a subset of Bloc (Bloc extends Cubit) so you can think of Cubit as a simplified Bloc which has less functionality. Blocs are more powerful than Cubits but Cubits are more simple.

Like I said all of these questions will be covered in great detail once v6.0.0 is out so stay tuned 👍

Peng-Qian commented 4 years ago

Thanks for your explanations!~ @felangel 👍

HerrNiklasRaab commented 4 years ago

@felangel Hey, could you make a small example, how Cubit makes for example debounce impossible. Didn't got that one.

PS: Thanks for this amazing package so far!

narcodico commented 4 years ago

@HerrNiklasRaab cubit doesn't make debounce impossible, but you'd have to implement a mechanism for your methods to be debounced. As for with bloc, this is really easy to achieve.

HerrNiklasRaab commented 4 years ago

Got it thanks!

dave commented 4 years ago

Another thing I've found hard with my cubit app is a UI to allow a generic error message to include a "retry" button. It's my first flutter app, so I haven't used bloc before, but I would imagine with bloc I could just include the original event in the "error" state, and the view can just resubmit it. With cubit it's a little more complex.

HerrNiklasRaab commented 4 years ago

Could you make a small example?

dave commented 4 years ago

I can explain a bit further. I'm trying to make my app "offline first" so every UI operation that requires an active internet connection has a popup when the connection is offline, like this:

This popup is identical on a multitude of UI elements so I made a helper function that lives in the view layer - here's an example of calling it.

I also have generic functionality for error handling (that shows a popup with appropriate buttons), so I made a helper function for this too:

... this way, the cubit is relieved of all error handling duties and all the cubit's methods have to do is throw exceptions. These are caught by the helper function in the view layer and appropriate UI is shown.

I'm coming to the conclusion that this is logic is perhaps too complex to live in the view layer, and it should really live in my cubits. I've been thinking about how to migrate this to the cubit layer without enormous complexity and repeated code in every cubit method. The "try anyway" and "retry" buttons are the difficult bit.

Here's the app deployed to the web. Log in with email "a" and code "a". (Click the cloud at the top and click "go offline" to simulate being offline). The item named "broken-item" will throw an error when downloaded.

HerrNiklasRaab commented 4 years ago

I understand, but this complex no matter if you choose Cubit or Bloc. Did you have any impediments implementing this only with Cubits for example?

cedvdb commented 3 years ago

@felangel

2. No, since Cubit has no notion of events, things like debounce, switchMap, throttle, etc... will not be as easy to support with Cubit and you'd be better off using Bloc.

Meh, I disagree, actually I think Cubit should be used as a defacto because it removes the boilerplate that is often a complain about bloc. Bloc should be used only when needed, which isn't in the doc as mostly bloc is used.

With a cubit you'd have something like this (semi pseudo code)

class SearchCubit extends Cubit<SearchState> {
  StreamController<String> _search$ = StreamController<String>();
  StreamSubscription searchSubscription;

  SearchCubit() {
    searchSubscription = _search$.stream
      .debounceTime(Duration(microseconds: 500))
      .listen(_doSearch);
  }

  search(String str) {
    _search$.add(str);
  }

  _doSearch(String str) async {
    try {
      emit(SearchLoading());
      final result = await searchService.search(str);
      emit(SearchSuccess(result));
    } catch (e) { 
      emit(SeachFailure())
    }
  }

  // dispose subscription
}

It's arguably simpler with bloc but even then it comes with a few pain points:

  1. Bloc has an indirection layer of Event and mapEventToState that comes with boiler plate.
  2. When you architecture an app it's better to have the same pattern everywhere, so using bloc or cubit as a standard, not both interchangably.
  3. A search debounce is something you find in just some places of the application, the rest of the state probably doesn't need to have things like that. Therefor your whole app comes with unneeded boiler plates (pt 2) for something that can easily be implemented in a cubit using rxdart in a few places.

Not to mention that if you already know rx you can do it easier in a cubit, in a bloc you'd have to understand what the transformEvent stream is. While Rx library is something that is useful even across languages (rxjs, rxjava, etc.).

My point is: Making a whole app use bloc to not use debounce on a "manual" stream and use debounce on the transformEvent stream is overkill. You are better off using cubit everywhere and just have a private stream that you debounce like above in the few places that you need it than making your whole app use Bloc with the boiler plates that goes with it.

In the doc you suggest traceability which I agree with

There could be many reasons as to why the application's state could change from authenticated to unauthenticated. For example, the user might have tapped a logout button and requested to be signed out of the application. On the other hand, maybe the user's access token was revoked and they were forcefully logged out. When using Bloc we can clearly trace how the application state got to a certain state.

I can get behind that but for most applications this is over engineering, you don't need that level traceability. You just care that the user is logged out. The way the doc is done it suggest bloc should be used (it's also the name of the lib), what I suggest is cubit should be used in most applications then bloc when you need it, which should be rare. It's a shame because imo the lib would be more popular with cubit as the defacto.