bloc_event_status 1.0.0 copy "bloc_event_status: ^1.0.0" to clipboard
bloc_event_status: ^1.0.0 copied to clipboard

Track the status of events in a bloc without updating the state.

BlocEventStatus #

BlocEventStatus CI codecov pub package pub points pub Popularity pub Likes License: MIT

Track the status of events in a bloc without updating the state.

Installation #

Add the package to your pubspec.yaml:

dependencies:
  bloc_event_status: ^1.0.0

and then run:

flutter pub get

Or just install it with flutter cli:

flutter pub add bloc_event_status

Getting Started #

Update the Bloc #

Start by creating a Bloc as usual:

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  TodoBloc() : super(const TodoState.initial()) {
    on<TodoLoadRequested>(_onLoadRequested);
  }

  Future<void> _onLoadRequested(
    TodoLoadRequested event,
    Emitter<TodoState> emit,
  ) async {
    try {
      final todos = await loadTodos();

      emit(state.copyWith(todos: todos));
    } on Exception catch (e) {
      addError(e);
      return;
    }
  }
}

Add the BlocEventStatusMixin to your Bloc class:

class TodoBloc extends Bloc<TodoEvent, TodoState> with BlocEventStatusMixin<TodoEvent, TodoState> {
  /* ... */
}

Now you can use emitEventStatus to emit the status of the event:

/* ... */

Future<void> _onLoadRequested(
  TodoLoadRequested event,
  Emitter<TodoState> emit,
) async {
  emitEventStatus(event, LoadingEventStatus()); // Emit loading status

  try {
    final todos = await loadTodos();

    emit(state.copyWith(todos: todos));

    emitEventStatus(event, SuccessEventStatus()); // Emit success status
  } on Exception catch (e) {
    emitEventStatus(event, FailureEventStatus(e)); // Emit failure status

    addError(e);
    return;
  }
}

/* ... */

When using BlocEventStatusMixin you also get access to some useful methods:

  • emitLoadingStatus() - Emit loading status (equivalent to emitEventStatus(event, LoadingEventStatus()))
  • emitSuccessStatus(data) - Emit success status (equivalent to emitEventStatus(event, SuccessEventStatus(data)))
  • emitFailureStatus(error) - Emit failure status (equivalent to emitEventStatus(event, FailureEventStatus(error)))

Let's see them in action:

/* ... */

Future<void> _onLoadRequested(
  TodoLoadRequested event,
  Emitter<TodoState> emit,
) async {
  emitLoadingStatus(event);

  try {
    final todos = await loadTodos();

    emit(state.copyWith(todos: todos));

    emitSuccessStatus(event);
  } on Exception catch (e) {
    emitFailureStatus(event, error: e);

    addError(e);
    return;
  }
}

/* ... */

The most powerful method of BlocEventStatusMixin is handleEventStatus. It allows you to wrap your already existing event handler and automatically emit the statuses of the event for you. Let's see how to use it:

class TodoBloc extends Bloc<TodoEvent, TodoState> with BlocEventStatusMixin<TodoEvent, TodoState> {
  TodoBloc() : super(const TodoState.initial()) {
    on<TodoLoadRequested>(handleEventStatus(_onLoadRequested)); // Wrap the event handler with handleEventStatus
  }

  Future<void> _onLoadRequested(
    TodoLoadRequested event,
    Emitter<TodoState> emit,
  ) async {
    final todos = await loadTodos();

    emit(state.copyWith(todos: todos));
  }
}

React to Event Statuses #

Now that you have your Bloc set up, you can listen to the event statuses in your UI.

BlocEventStatusListener

You can use the BlocEventStatusListener widget to listen to one event status in your UI.

BlocEventStatusListener<TodoBloc, TodoEvent, TodoLoadRequested>(
  // optionally filter the events to listen to
  filter: (event) => /* select only the events you want */,
  // optionally select when the listener should be called
  listenWhen: (previous, current) => previous != current && current is FailureEventStatus,
  // The listener function that will be called when a new status is emitted
  listener: (context, event, status) {
    final error = (status as FailureEventStatus).error;
    final messenger = ScaffoldMessenger.of(context);
    messenger
      ..hideCurrentSnackBar()
      ..showSnackBar(
        SnackBar(
          content: Text(
            'Error loading todos: $error',
          ),
          action: SnackBarAction(
            label: "Retry",
            onPressed: () {
              messenger.hideCurrentSnackBar();
              context.read<TodoBloc>().add(event);
            },
          ),
        ),
      );
  },
  bloc: subjectBloc,
  child: SomeWidget(),
)

BlocEventStatusBuilder

You can use the BlocEventStatusBuilder widget to build your UI based on the event status. It's equivalent to BlocEventStatusListener but instead of a listener function, it takes a builder function that returns a widget.

BlocEventStatusBuilder<TodoBloc, TodoEvent, TodoDeleted, TodoState>(
  filter: (event) => event.todo.id == todos[index].id,
  buildWhen: (previous, current) =>
      previous != current &&
      (previous is LoadingEventStatus || current is LoadingEventStatus),
  builder: (context, event, status) {
    if (status is LoadingEventStatus) {
      return CircularProgressIndicator();
    }

    return IconButton(
      icon: Icon(Icons.delete),
      onPressed: () {
        context.read<TodoBloc>().add(TodoDeleted(todos[index]));
      },
    );
  },
),

BlocBuilderWithEventStatus

In case you want to use the BlocBuilder widget and listen to the event status at the same time, you can use the BlocBuilderWithEventStatus widget.

BlocBuilderWithEventStatus<TodoBloc, TodoEvent, TodoToggled, TodoState>(
  filter: (event) => event.todo.title == 'Learn Flutter',
  // optionally select when the builder should be called based on the status
  buildWhenStatus: (previous, current) =>
      previous != current &&
      (previous is LoadingEventStatus || current is LoadingEventStatus),
  // optionally select when the builder should be called based on the state
  buildWhenState: (previous, current) =>
      previous.filter != current.filter && current.filter == Filter.done,
  builder: (context, event, status, state) {
    if (status is LoadingEventStatus) {
      return const CircularProgressIndicator();
    }

    return const Text('Flutter is awesome! Here is the state: $state');
  },
  bloc: subjectBloc,
  child: SomeWidget(),
)

Advanced Usage #

React to multiple event types #

Every widget that reacts to a specific event type has it corresponding widget that reacts to multiple event types. For example, BlocEventStatusListener has MultiBlocEventStatusListener. The only difference is that you can't specify a specific event type in it's type parameters but you can use the filter parameter to filter the events you want to listen to.

MultiBlocEventStatusListener<TodoBloc, TodoEvent>(
  filter: (event) => event is TodoLoadRequested || event is TodoDeleted,
  listener: (context, event, status) {
    // Handle the event status here
  },
)

If you want to listen to all events, you can just ignore the filter parameter.

MultiBlocEventStatusListener<TodoBloc, TodoEvent>(
  listener: (context, event, status) {
    // Handle the event status here
  },
)

Custom Event Statuses #

In order to use your own event statuses, you need to use BlocCustomEventStatusMixin mixin instead of BlocEventStatusMixin.

enum MyStatus { initial, loading, success, failure }

class TodoBloc extends Bloc<TodoEvent, TodoState> with BlocCustomEventStatusMixin<TodoEvent, TodoState, MyStatus> {
  TodoBloc() : super(const TodoState.initial()) {
    on<TodoLoadRequested>(_onLoadRequested);
  }

  Future<void> _onLoadRequested(
    TodoLoadRequested event,
    Emitter<TodoState> emit,
  ) async {
    emitEventStatus(event, MyStatus.loading); // Emit loading status

    try {
      final todos = await loadTodos();

      emit(state.copyWith(todos: todos));

      emitEventStatus(event, MyStatus.success); // Emit success status
    } on Exception catch (e) {
      emitEventStatus(event, MyStatus.failure); // Emit failure status

      addError(e);
      return;
    }
  }
}

If you use this mixin you need also to change the widgets from BlocEventStatus* to BlocCustomEventStatus*. For example, BlocEventStatusListener becomes BlocCustomEventStatusListener and so on.

Example #

See the example folder for a complete example of how to use the package.

Acknowledgments #

A special thanks to LeanCode for their inspiring work on the bloc_presentation package, which served as a foundational reference and inspiration for this project.

Contributing #

We welcome contributions! Please open an issue, submit a pull request or open a discussion on GitHub.

License #

This project is licensed under the MIT License.

2
likes
160
points
175
downloads

Publisher

verified publisherjacopoguzzo.dev

Weekly Downloads

Track the status of events in a bloc without updating the state.

Repository (GitHub)
View/report issues

Topics

#bloc #state-management

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

equatable, flutter, flutter_bloc, meta, nested

More

Packages that depend on bloc_event_status