Keyboard Shortcuts

j / kScroll down / up
ggScroll to top
GScroll to bottom
K / /Open search
?Show this help
EscClose search / help
n / NNext / previous section
hGo to landing
dGo to docs
:Command mode — type section name

Press ? or Esc to close

Deep Dives

State Management

All cubits extend Cubit<State>. State is immutable with copyWith(). Global cubits are lazy singletons; feature cubits are factories.

Cubit pattern — the default

lib/features/profile/logic/profile_cubit.dart
// State
class ProfileState {
  final SimpleApiResponse<ProfileModel> userProfileResponse;
  final SimpleApiResponse<bool> certificateMutationStatus;

  const ProfileState({
    required this.userProfileResponse,
    required this.certificateMutationStatus,
  });

  factory ProfileState.initial() => ProfileState(
    userProfileResponse: ApiResponse.initial(data: ProfileModel.empty()),
    certificateMutationStatus: ApiResponse.initial(data: false),
  );

  ProfileState copyWith({
    SimpleApiResponse<ProfileModel>? userProfileResponse,
    SimpleApiResponse<bool>? certificateMutationStatus,
  }) => ProfileState(
    userProfileResponse: userProfileResponse ?? this.userProfileResponse,
    certificateMutationStatus: certificateMutationStatus ?? this.certificateMutationStatus,
  );
}

// Cubit
class ProfileCubit extends Cubit<ProfileState> {
  final ProfileStore _store;

  ProfileCubit(this._store) : super(ProfileState.initial());

  Future<void> fetchMyProfile() async {
    emit(state.copyWith(userProfileResponse: state.userProfileResponse.loading));
    final result = await _store.getMyProfile();
    emit(state.copyWith(userProfileResponse: result));
  }

  Future<void> createCertificate(CertificateBody body) async {
    emit(state.copyWith(certificateMutationStatus: ApiResponse(status: ActionStatus.loading, data: false)));
    final result = await _store.createCertificate(body);
    emit(state.copyWith(
      certificateMutationStatus: result.isSuccess
          ? ApiResponse(status: ActionStatus.success, data: true)
          : ApiResponse(status: ActionStatus.failure, message: result.message, data: false),
    ));
    if (result.isSuccess) await fetchMyProfile();
  }
}

PaginatedCubit — for list screens

lib/config/network/paginated_cubit/offset_paginated_cubit/paginated_cubit.dart
abstract class PaginatedCubit<ItemT, FilterT, ExtraT>
    extends Cubit<OffsetPaginatedState<ItemT, FilterT, ExtraT>>
    with ConnectivityReloadMixin {

  // Subclass implements only this:
  Future<ApiResponse<List<ItemT>, OffsetPaginationMeta>> fetchPage(int page, FilterT? filters);

  // Provided out of the box:
  Future<void> loadFirst() async { ... }    // load page 1, replace list
  Future<void> loadMore() async { ... }     // load next page, append
  Future<void> refresh() async { ... }      // pull-to-refresh, reset to page 1
  Future<void> applyFilters(FilterT f) async { ... }  // set filters + reload
  Future<void> clearFilters() async { ... } // reset filters + reload
}

UI access pattern

lib/features/profile/presentation/screens/profile_screen.dart
BlocBuilder<ProfileCubit, ProfileState>(
  builder: (context, state) {
    if (state.userProfileResponse.isLoading) return const AppShimmer();
    if (state.userProfileResponse.isFailure) return AppEmptyState.error();
    return ProfileContent(data: state.userProfileResponse.data);
  },
);

@override
void initState() {
  super.initState();
  context.read<ProfileCubit>().fetchMyProfile(); // always in initState
}

Registration Rules

TypeRegistrationScope
Feature cubitsregisterFactoryNew instance per BlocProvider
Global cubitsregisterLazySingletonShared across entire app

Global cubits: GlobalErrorCubit · ThemeCubit · LocalizationCubit · ConnectivityCubit · LoadingCubit · ToastCubit

sam's arch — A Pragmatic Flutter Architecture