Bloc with Freezed and Built Value

Thomas Gazzoni

4 min read

Among the all State Management library in Flutter you might have heard about the Bloc pattern and maybe struggle a bit with all the redundant boilerplate code you have to copy and paste around.

In this article we will see a way to integrate Freezed and Built Value to reduce the Bloc pattern boilerplate code.

The Problem#

While working on a new Flutter project I found the Bloc pattern a bit too verbose and I didn't like to use Equitable since I was already using the Built Value library.

Another issue I found is the Bloc is operator, in my opinion, is quite dangerous, since it doesn't care of the base type you are comparing the event or state, and if you are not careful enough you may end up comparing events with states.

Of course you can (and you should) write tests, and those will point out that there is some problem with the mapEventToState function, but I think is still better to have a sort of intellisense check while you write the code.

The Solution#

I try to convert my Bloc Event and State using Built Value, to reduce a bit the code and remove the Equitable library dependence.

For the State is possible to use Built Value with different factories, but for Events is not possible since you can't check if a State object is belong to a specific factory, so you still need to use normal classes and the is operator.

I search around for a better solutions, asking in StackOverflow I got this answer and give it a try.

The Result#

Firstly, we need to install those libraries:

Using Freezed the bloc Event and State are definitely less verbose and looks cleaner:


// Events

abstract class CourseEvent with _$CourseEvent {
  const factory CourseEvent.fetchList() = FetchList;
  const factory CourseEvent.fetchError([String message]) = FetchError;
}

// States

abstract class CourseState with _$CourseState {
  const factory CourseState({([]) List<Course> value}) = Initial;
  const factory CourseState.loading() = Loading;
  const factory CourseState.success(BuiltList<Course> value) = Success;
  const factory CourseState.failure([String message]) = Failure;
}

While in our Bloc we have a mapEventToState we don't use the is operator but we can use the Freezed when method and yield different states:

class CourseBloc extends Bloc<CourseEvent, CourseState> {
  final CourseRepository _courseRepository;

  CourseBloc(this._courseRepository) : super();

  
  CourseState get initialState => CourseState();

  
  Stream<CourseState> mapEventToState(event) {
    return event.maybeWhen(
      fetchList: () async* {
        try {
          yield CourseState.loading();
          final courses = await _courseRepository.getDefaultList();
          yield CourseState.success(courses);
        } catch (error) {
          yield CourseState.failure(error.toString());
        }
      },
      orElse: () async* {
        yield CourseState.failure('Event not Implemented');
      },
    );
  }
}

NOTE: We use the maybeWhen with orElse to be more flexible in this example, so if you forget to implement an event it will just throw a error.

In production will be better to just use the when so it will be mandatory to implement all the events.

In the Widget we also use the Freezed when method to return the Widget base on the state.

    child: BlocConsumer<CourseBloc, CourseState>(
        builder: (context, state) => state.when(
          (value) => Center(
            child: RaisedButton(
              child: Text('Load courses'),
              onPressed: () {
                _courseBloc.add(CourseEvent.fetchList());
              },
            ),
          ),
          loading: () => Center(child: CircularProgressIndicator()),
          success: (data) => ListView.builder(
            itemCount: data?.length ?? 0,
            itemBuilder: (_, index) => CourseItem(data[index], index),
          ),
          failure: (message) => Padding(
            padding: EdgeInsets.all(20),
            child: Text('Error while fetching data: $message')
        ),
    ),

The Conclusion#

As you can see our example Bloc code is quite compact and cleaner, it could be put in a single file, reducing the complexity of our Blocs especially in large Flutter apps projects.

You can see this full example here


Subscribe to the newsletter