Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BLoC - Should every state be a separate class?

I'm learning and building a Flutter app using BLoC pattern and in a lot of tutorials and repositories, I have seen people having a separate class for each state of the BLoC and others which have a single state class with all the properties defined. Is there a standard BLoC way for defining state classes or is it a personal choice?

Example with multiple state classes

abstract class LoginState extends Equatable {
  LoginState([List props = const []]) : super(props);
}

class LoginInitial extends LoginState {
  @override
  String toString() => 'LoginInitial';
}

class LoginLoading extends LoginState {
  @override
  String toString() => 'LoginLoading';
}

class LoginFailure extends LoginState {
  final String error;

  LoginFailure({@required this.error}) : super([error]);

  @override
  String toString() => 'LoginFailure { error: $error }';
}

Example with a single state class

@immutable
class MyFormState extends Equatable {
  final String email;
  final bool isEmailValid;
  final String password;
  final bool isPasswordValid;
  final bool formSubmittedSuccessfully;

  bool get isFormValid => isEmailValid && isPasswordValid;

  MyFormState({
    @required this.email,
    @required this.isEmailValid,
    @required this.password,
    @required this.isPasswordValid,
    @required this.formSubmittedSuccessfully,
  }) : super([
          email,
          isEmailValid,
          password,
          isPasswordValid,
          formSubmittedSuccessfully,
        ]);

  factory MyFormState.initial() {
    return MyFormState(
      email: '',
      isEmailValid: false,
      password: '',
      isPasswordValid: false,
      formSubmittedSuccessfully: false,
    );
  }

  MyFormState copyWith({
    String email,
    bool isEmailValid,
    String password,
    bool isPasswordValid,
    bool formSubmittedSuccessfully,
  }) {
    return MyFormState(
      email: email ?? this.email,
      isEmailValid: isEmailValid ?? this.isEmailValid,
      password: password ?? this.password,
      isPasswordValid: isPasswordValid ?? this.isPasswordValid,
      formSubmittedSuccessfully:
          formSubmittedSuccessfully ?? this.formSubmittedSuccessfully,
    );
  }

  @override
  String toString() {
    return '''MyFormState {
      email: $email,
      isEmailValid: $isEmailValid,
      password: $password,
      isPasswordValid: $isPasswordValid,
      formSubmittedSuccessfully: $formSubmittedSuccessfully
    }''';
  }
}

Which one should be used when? What's the advantage and disadvantage between both?

like image 414
JavaBanana Avatar asked Jan 22 '26 07:01

JavaBanana


2 Answers

It's more of a personal choice / coding style but I agree that some way might be better than other depending on the scenario.

Note that Bloc doc now states the two methods.

There a few differences though. First, when using multiple state classes you have greater control about the fields of each state. For example if you know that an error can only happen in some case it might be better to isolate this in a specific class BlocSubjectError instead of having a class containing a nullable error that might or might not be there. From a caller perspective the conditions are also a bit different, you might prefer one way or the other :

With multiple state classes

if(state is BlocSubjectLoading) {
  // display loading indicator
}
else if (state is BlocSubjectError) {
  // display errror
} else if (state is BlocSubjectSuccess) {
  // display data
}

With single state class

if(state.isLoading) {
  // display loading indicator
}
else if (state.error != null) {
  // display errror
} else if (state.data != null) {
  // display data
}

Though it might not look like a big difference, the second example allows for mixed states : you could have both an error and a data, or both being loading and having a previous data. It might or might be what you desire. In the first case the types make this more constrained which make this technique closer to representing the state of your app with types, a.k.a. Type Driven Development.

On the other hand, having multiple state classes might be cumbersome if they all declare the same fields because you will need to instantiate your states with all the parameters each time whereas using a single class you can just call a .copyWith() method to which you will only give changed parameters.

The single state class pattern is close to Triple Pattern (or Segmented State Pattern). You can find a comprehensive workshop that describes this pattern with Bloc here.

like image 121
Pom12 Avatar answered Jan 24 '26 23:01

Pom12


Just wanted to share a technique to improve the syntax for single-state classes as mentioned by @Pom12:

Say you have a "AuthState" single-state class, this class can hold a status field with extension utilities:

enum AuthStatus { initial, unauthenticated, authenticated }

extension AuthStatusX on AuthStatus {
  bool get isInitial => this == AuthStatus.initial;
  bool get isUnauthenticated => this == AuthStatus.unauthenticated;
  bool get isAuthenticated => this == AuthStatus.authenticated;
}

Then you are able to query the state status cleanly as follows:

if (state.status.isAuthenticated)

instead of:

if (state.status.authenticated != null)

P.S. I picked up this technique form VeryGoodVentures, see example here.

like image 36
Asaf Avatar answered Jan 24 '26 21:01

Asaf



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!