跳转到内容

FAQs

此内容尚不支持你的语言。

State Not Updating

Question: I’m emitting a state in my bloc but the UI is not updating. What am I doing wrong?

💡 Answer: If you’re using Equatable make sure to pass all properties to the props getter.

GOOD

my_state.dart
sealed class MyState extends Equatable {
const MyState();
}
final class StateA extends MyState {
final String property;
const StateA(this.property);
@override
List<Object> get props => [property]; // pass all properties to props
}

BAD

my_state.dart
sealed class MyState extends Equatable {
const MyState();
}
final class StateA extends MyState {
final String property;
const StateA(this.property);
@override
List<Object> get props => [];
}
my_state.dart
sealed class MyState extends Equatable {
const MyState();
}
final class StateA extends MyState {
final String property;
const StateA(this.property);
@override
List<Object> get props => null;
}

In addition, make sure you are emitting a new instance of the state in your bloc.

GOOD

my_bloc.dart
MyBloc() {
on<MyEvent>((event, emit) {
// always create a new instance of the state you are going to yield
emit(state.copyWith(property: event.property));
});
}
my_bloc.dart
MyBloc() {
on<MyEvent>((event, emit) {
final data = _getData(event.info);
// always create a new instance of the state you are going to yield
emit(MyState(data: data));
});
}

BAD

my_bloc.dart
MyBloc() {
on<MyEvent>((event, emit) {
// never modify/mutate state
state.property = event.property;
// never emit the same instance of state
emit(state);
});
}

When to use Equatable

Question: When should I use Equatable?

💡Answer:

my_bloc.dart
MyBloc() {
on<MyEvent>((event, emit) {
emit(StateA('hi'));
emit(StateA('hi'));
});
}

In the above scenario if StateA extends Equatable only one state change will occur (the second emit will be ignored). In general, you should use Equatable if you want to optimize your code to reduce the number of rebuilds. You should not use Equatable if you want the same state back-to-back to trigger multiple transitions.

In addition, using Equatable makes it much easier to test blocs since we can expect specific instances of bloc states rather than using Matchers or Predicates.

my_bloc_test.dart
blocTest(
'...',
build: () => MyBloc(),
act: (bloc) => bloc.add(MyEvent()),
expect: [
MyStateA(),
MyStateB(),
],
);

Without Equatable the above test would fail and would need to be rewritten like:

my_bloc_test.dart
blocTest(
'...',
build: () => MyBloc(),
act: (bloc) => bloc.add(MyEvent()),
expect: [
isA<MyStateA>(),
isA<MyStateB>(),
],
);

Handling Errors

Question: How can I handle an error while still showing previous data?

💡 Answer:

This highly depends on how the state of the bloc has been modeled. In cases where data should still be retained even in the presence of an error, consider using a single state class.

my_state.dart
enum Status { initial, loading, success, failure }
class MyState {
const MyState({
this.data = Data.empty,
this.error = '',
this.status = Status.initial,
});
final Data data;
final String error;
final Status status;
MyState copyWith({Data data, String error, Status status}) {
return MyState(
data: data ?? this.data,
error: error ?? this.error,
status: status ?? this.status,
);
}
}

This will allow widgets to have access to the data and error properties simultaneously and the bloc can use state.copyWith to retain old data even when an error has occurred.

my_bloc.dart
on<DataRequested>((event, emit) {
try {
final data = await _repository.getData();
emit(state.copyWith(status: Status.success, data: data));
} catch(error) {
emit(state.copyWith(status: Status.failure, error: 'Something went wrong!'));
}
});

Bloc vs. Redux

Question: What’s the difference between Bloc and Redux?

💡 Answer:

BLoC is a design pattern that is defined by the following rules:

  1. Input and Output of the BLoC are simple Streams and Sinks.
  2. Dependencies must be injectable and Platform agnostic.
  3. No platform branching is allowed.
  4. Implementation can be whatever you want as long as you follow the above rules.

The UI guidelines are:

  1. Each “complex enough” component has a corresponding BLoC.
  2. Components should send inputs “as is”.
  3. Components should show outputs as close as possible to “as is”.
  4. All branching should be based on simple BLoC boolean outputs.

The Bloc Library implements the BLoC Design Pattern and aims to abstract RxDart in order to simplify the developer experience.

The three principles of Redux are:

  1. Single source of truth
  2. State is read-only
  3. Changes are made with pure functions

The bloc library violates the first principle; with bloc state is distributed across multiple blocs. Furthermore, there is no concept of middleware in bloc and bloc is designed to make async state changes very easy, allowing you to emit multiple states for a single event.

Bloc vs. Provider

Question: What’s the difference between Bloc and Provider?

💡 Answer: provider is designed for dependency injection (it wraps InheritedWidget). You still need to figure out how to manage your state (via ChangeNotifier, Bloc, Mobx, etc…). The Bloc Library uses provider internally to make it easy to provide and access blocs throughout the widget tree.

BlocProvider.of() Fails to Find Bloc

Question: When using BlocProvider.of(context) it cannot find the bloc. How can I fix this?

💡 Answer: You cannot access a bloc from the same context in which it was provided so you must ensure BlocProvider.of() is called within a child BuildContext.

GOOD

my_widget.dart
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => BlocA(),
child: MyChild();
);
}
class MyChild extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
final blocA = BlocProvider.of<BlocA>(context);
...
},
)
...
}
}
my_widget.dart
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => BlocA(),
child: Builder(
builder: (context) => ElevatedButton(
onPressed: () {
final blocA = BlocProvider.of<BlocA>(context);
...
},
),
),
);
}

BAD

my_widget.dart
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => BlocA(),
child: ElevatedButton(
onPressed: () {
final blocA = BlocProvider.of<BlocA>(context);
...
}
)
);
}

Project Structure

Question: How should I structure my project?

💡 Answer: While there is really no right/wrong answer to this question, some recommended references are

The most important thing is having a consistent and intentional project structure.

Adding Events within a Bloc

Question: Is it okay to add events within a bloc?

💡 Answer: In most cases, events should be added externally but in some select cases it may make sense for events to be added internally.

The most common situation in which internal events are used is when state changes must occur in response to real-time updates from a repository. In these situations, the repository is the stimulus for the state change instead of an external event such as a button tap.

In the following example, the state of MyBloc is dependent on the current user which is exposed via the Stream<User> from the UserRepository. MyBloc listens for changes in the current user and adds an internal _UserChanged event whenever a user is emitted from the user stream.

my_bloc.dart
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc({required UserRepository userRepository}) : super(...) {
on<_UserChanged>(_onUserChanged);
_userSubscription = userRepository.user.listen(
(user) => add(_UserChanged(user)),
);
}
}

By adding an internal event, we are also able to specify a custom transformer for the event to determine how multiple _UserChanged events will be processed — by default they will be processed concurrently.

It’s highly recommended that internal events are private. This is an explicit way of signaling that a specific event is used only within the bloc itself and prevents external components from knowing about the event.

my_event.dart
sealed class MyEvent {}
// `EventA` is an external event.
final class EventA extends MyEvent {}
// `EventB` is an internal event.
// We are explicitly making `EventB` private so that it can only be used
// within the bloc.
final class _EventB extends MyEvent {}

We can alternatively define an external Started event and use the emit.forEach API to handle reacting to real-time user updates:

my_bloc.dart
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc({required UserRepository userRepository})
: _userRepository = userRepository, super(...) {
on<Started>(_onStarted);
}
Future<void> _onStarted(Started event, Emitter<MyState> emit) {
return emit.forEach(
_userRepository.user,
onData: (user) => MyState(...)
);
}
}

The benefits of the above approach are:

  • We do not need an internal _UserChanged event
  • We do not need to manage the StreamSubscription manually
  • We have full control over when the bloc subscribes to the stream of user updates

The drawbacks of the above approach are:

  • We cannot easily pause or resume the subscription
  • We need to expose a public Started event which must be added externally
  • We cannot use a custom transformer to adjust how we react to user updates

Exposing Public Methods

Question: Is it okay to expose public methods on my bloc and cubit instances?

💡 Answer

When creating a cubit, it’s recommended to only expose public methods for the purposes of triggering state changes. As a result, generally all public methods on a cubit instance should return void or Future<void>.

When creating a bloc, it’s recommended to avoid exposing any custom public methods and instead notify the bloc of events by calling add.