Cubit is a subset of the BLoC package that does not rely on events and instead uses methods to emit new states. Cubits are used for simple states, while for more complicated scenarios (multiple states) we use blocs.
This is a snippet for an example cubit. For more info on BloCs check the Sign in with phone number authentication & Firebase
or the Monitoring authentication status
blogs here, or this link: https://bloclibrary.dev/
Dependencies
Add the following packages in the pubspec.yaml first.
- hydrated_bloc: ^version
- json_annotation: ^version
- equatable: ^version
and run the pub get command from the terminal.
Example Cubit
feed_state.dart
part 'feed_state.g.dart'; // --> type this. It's the same file name but with the .g.dart extention. To auto generate this run the `flutter packages pub run build_runner build --delete-conflicting-outputs` command in the terminal.
enum FeedStatus { initial, loading, success, failure }
extension FeedStatusX on FeedStatus {
bool get isInitial => this == FeedStatus.initial;
bool get isLoading => this == FeedStatus.loading;
bool get isSuccess => this == FeedStatus.success;
bool get isFailure => this == FeedStatus.failure;
}
@JsonSerializable()
class FeedState extends Equatable {
const FeedState({
this.status = FeedStatus.initial,
this.feed,
});
factory FeedState.fromJson(Map<String, dynamic> json) =>
_$FeedStateFromJson(json);
final FeedStatus status;
final Feed feed;
FeedState copyWith({
FeedStatus status,
Feed feed,
}) {
return FeedState(
status: status ?? this.status,
feed: feed ?? this.feed,
);
}
Map<String, dynamic> toJson() => _$FeedStateToJson(this);
@override
List<Object> get props => [status, /*temperatureUnits,*/ feed];
}
feed_cubit.dart
class FeedCubit extends HydratedCubit<FeedState> {
FeedCubit(this._repository) : super(const FeedState());
final Repository _repository;
Future<void> fetchFeed() async {
emit(state.copyWith(status: FeedStatus.loading));
try {
var feed = await _repository.getFeed();
emit(
state.copyWith(
status: FeedStatus.success,
feed: feed),
);
} on Exception {
emit(state.copyWith(status: FeedStatus.failure));
}
}
Future<void> refreshFeed() async {
if (!state.status.isSuccess) return;
try {
final feed = await _repository.getFeed();
emit(
state.copyWith(
status: FeedStatus.success,
feed: feed,
),
);
} on Exception {
emit(state);
}
}
@override
FeedState fromJson(Map<String, dynamic> json) => FeedState.fromJson(json);
@override
Map<String, dynamic> toJson(FeedState state) => state.toJson();
}
Example usage
class FeedStateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<FeedCubit>(
create: (context) =>
FeedCubit(context.read<Repository>())..fetchFeed(),
),
...
],
child: FeedPage(),
),
}
class FeedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: BlocConsumer<FeedCubit, FeedState>(
listener: (context, state) {
// no-op
},
builder: (context, state) {
switch (state.status) {
case FeedStatus.initial:
return const FeedLoading();
case FeedStatus.loading:
return const FeedLoading();
case FeedStatus.success:
return state.feed != null && state.feed.videoList.isNotEmpty
? _buildPopulated(state.feed)
: _buildEmptyView();
case FeedStatus.failure:
return const FeedError();
}
return Container(
child: Text(Strings.loading),
);
},
),
);
}
}