Flutter Cubit example

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.

  1. hydrated_bloc: ^version
  2. json_annotation: ^version
  3. 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),
          );
        },
      ),
    );
  }
}