felangel / bloc

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

feat: Add optional type argument to represent "from state" in `on` method #4217

Open ishchhabra opened 1 month ago

ishchhabra commented 1 month ago

Description

In a state machine, state transitions occur from a particular state to another state. Currently, the on<...> method in BLoC does not provide an explicit way to specify the "from state" for a transition. The proposal suggests adding an optional type argument to the on<...> method to specify the state from which the transition is happening.

Motivation

Currently, BLoC code allows developers to identify the event that caused a transition (from the on<Event> method) and the state being transitioned to (from the emit(state) calls). However, the "from state" information is not explicitly captured, making it challenging to model the state machine directly from BLoC code. Developers often have to implement their own guardrails (e.g., with assertions or type casts) to enforce specific state transitions or rely on implicit assumptions about the BLoC's usage. Adding the "from state" parameter would eliminate the need for these custom guardrails, providing a complete picture of the state transitions directly within the BLoC code. This enhancement would also enable easy generation of visualizations of the state machine by parsing the on<> and emit calls..

felangel commented 1 month ago

Hi @ishchhabra 👋 Thanks for opening an issue!

This seems like it would be best suited as a different package (there are already some like https://pub.dev/packages/state_graph_bloc).

Let me know if that helps 👍

ishchhabra commented 1 month ago

Hi @felangel,

Thanks for pointing out the existing alternatives. I explored some separate packages, but many diverge significantly from BLoC's API design, which forces a choice between different approaches. Including this functionality directly in BLoC as an opt-in feature avoids that problem. I believe BLoC’s API design is well-suited for this integration. I tried adding it as a separate package but was blocked by Dart’s lack of overloading. Given BLoC’s focus on predictable state management, integrating this feature seems like a natural fit, as it completes the mental model of a BLoC by including the state from which transitions occur.

What do you think?

JavertArdo commented 1 month ago

I'm using the BLoC library for the couple of years now. The same idea of explicit 'from state' declaration has been on my mind for a long time. During code implementation I always dealing with the complexity of the features and the code readability.

Let's have a look at simple example.

abstract class ExampleEvent {}

class FirstExampleEvent extends ExampleEvent {}
class SecondExampleEvent extends ExampleEvent {}
abstract class ExampleState {}

class DefaultExampleState extends ExampleState {}
class FirstExampleState extends ExampleState {}
class SecondExampleState extends ExampleState {}

To handle incoming events, usually the code is written like below. on<...> method registers the callback function, which should be executed when concrete event is dispatched. Additionally, events can be transformed to throttle them, delay, etc.

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  ExampleBloc() : super(DefaultExampleState()) {
    on<FirstExampleEvent>(
      (event, emit) async {
        if (state is DefaultExampleState) {
          // do the rest
        }
        if (state is FirstExampleState) {
          // do the rest
        }
      },
      transformer: concurrent(),
    );
    on<SecondExampleEvent>(
      (event, emit) async {
        if (state is SecondExampleState) {
          // do the rest
        }
      },
      transformer: sequential(),
    );
  }
}

Wouldn't be easier for end-user to define explicitly on which event and in which state the logic should be executed? Instead of registering on<...> methods and creating weird if statements and unnecessarily nesting the code.

There could be some other benefit to transform events differently (a pair of event and state instead of only event).

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  ExampleBloc() : super(DefaultExampleState()) {
    on<FirstExampleEvent, DefaultExampleState>(
      (event, emit) async {
        // do the rest
      },
      transformer: sequential(),
    );
    on<FirstExampleEvent, FirstExampleState>(
      (event, emit) async {
        // do the rest
      },
      transformer: concurrent(),
    );
    on<SecondExampleEvent, SecondExampleState>(
      (event, emit) async {
        // do the rest
      },
      transformer: sequential(),
    );
  }
}

Sometimes the if statements checking the states are much more complicated, excluding some states or substates.

The another benefit with explicit on<Event, State> declaration could be easier detection of abnormal code handling in wrong states. This might happen when multiple BLoC modules interact with each other and doing some code in parallel. If the code was executed on unexpected state, then throw exception to inform the developer about it, rather than do something unwanted.

What do you think about such improvement? I'm not an expert, so that is only the idea/concept.

aayushchhabra1999 commented 1 month ago

+1 on this. We also significantly need this at https://github.com/thekniru/. We have been internally using hacks to achieve this. Adding it to Bloc would significantly simplify things.

felangel commented 2 weeks ago

I'll think about this some more but as it is described, these changes would be very disruptive and breaking and there are many details of this type of API which require careful consideration.