Search code examples
flutterstatebloc

How can I access variables in Flutter Bloc without passing them as params to every single state?


So I have a Flutter app with Bloc for state mangement. I want to keep a list of users in Bloc so I can access it from different places in the app. But as it is now I pass this list of users to every state. Can I somehow avoid this boilerplate code?

Here is an example of my Bloc State:

@immutable
abstract class UsersState {
  final List<User> users;
  final User? user;

  const UsersState({required this.users, required this.user});
}

class UsersInitial extends UsersState {
  UsersInitial() : super(users: [], user: null);
}

class UsersLoadedState extends UsersState {
  const UsersLoadedState({required List<User> users, required User? user})
      : super(users: users, user: user);
}

class UsersPendingState extends UsersState {
  const UsersPendingState({List<User> users = const [], User? user})
      : super(users: users, user: user);
}


class UsersErrorState extends UsersState {
  final String message;
  const UsersErrorState(
      {required List<User> users, required User? user, required this.message})
      : super(users: users, user: user);
}

class FriendAddedState extends UsersState {
  const FriendAddedState({required List<User> users, required User? user})
      : super(users: users, user: user);
}

In this example I don't want to have to pass list of users to FriendAddedState. And here is how it looks in Bloc:

 else if (event is AddFriend) {
        emit(FriendAddPending(users: users, user: user));
        final result = await usecases.addFriend(event.friendId);
        result.fold((error) {
          emit(UsersErrorState(
              users: users, user: user, message: error.message));
        }, (success) {
          user = success;
          emit(FriendAddedState(users: users, user: user));
        });
      } 

Solution

  • You don't have to define users and user in UserState if you don't need them in all the bloc states. You could just define them in UsersLoadedState FriendAddedState like this:

    
    @immutable
    abstract class UsersState {
      const UsersState();
    }
    
    class UsersInitial extends UsersState {
      const UsersInitial();
    }
    
    class UsersLoadedState extends UsersState {
      const UsersLoadedState({
        required this.users,
        required this.user,
      });
    
      final List<User> users;
      final User? user;
    }
    
    class UsersPendingState extends UsersState {
      const UsersPendingState();
    }
    
    class UsersErrorState extends UsersState {
      final String message;
    
      const UsersErrorState({required this.message});
    }
    
    class FriendAddedState extends UsersState {
      const FriendAddedState({
        required this.users,
        required this.user,
      });
    
      final List<User> users;
      final User? user;
    }
    

    [Recommended] if you want to access user in every state of bloc, you can use a single class for your bloc state and an enum to define its state:

    @immutable
    class UsersState {
      final List<User> users;
      final User? user;
      final String? message;
      final UserStatus status;
    
      const UsersState({
        required this.status,
        required this.users,
        this.user,
        this.message,
      });
    
      const UsersState.initial()
          : status = UserStatus.initial,
            users = const [],
            user = null,
            message = null;
    
      UsersState copyWith(
        List<User>? users,
        User? user,
        String? message,
        UserStatus? status,
      ) {
        return UsersState(
          status: status ?? this.status,
          users: users ?? this.users,
          message: message ?? this.message,
          user: user ?? this.user,
        );
      }
    }
    
    enum UserStatus {
      initial,
      loaded,
      pending,
      error,
      friendAdded,
    }
    

    then in your bloc class, anywhere you wanted to emit new state, use state.copyWith()!