AsyncCubit templates, CRUD local updates, AsyncBlocBuilder, PaginatedCubit, executeMockOrAsync, and BlocListener patterns for Flutter_Base.
All cubits that do API calls extend AsyncCubit<T>. Never create raw Cubit for data-fetching.
abstract class AsyncCubit<T> extends Cubit<AsyncState<T>> {
// Built-in: setLoading(), setSuccess(data), setError(msg), reset(), updateData()
// Built-in: executeAsync(operation, successEmitter, showErrorToast)
// Built-in: executeMockOrAsync(mockData, operation) ← mock/real switch helper
// Built-in: baseCrudUseCase (auto-injected)
}
state.status // BaseStatus enum: initial, loading, loadingMore, success, error
state.data // T
state.errorMessage // String?
state.isInitial / state.isLoading / state.isSuccess / state.isError / state.isLoadingMore
بيشيل الـ
if (MockConfig.useMock)المتكرر — يخلي الـ cubit نضيف. Method موجود فيAsyncCubit— يتسخدم مباشرة بدون setup.
// Definition (inside AsyncCubit base class):
Future<void> executeMockOrAsync({
required T mockData,
required Future<Result<T, Failure>> Function() operation,
}) async {
if (MockConfig.useMock) {
setLoading();
await MockConfig.simulateDelay();
setSuccess(data: mockData);
return;
}
await executeAsync(operation: operation);
}
// ❌ BEFORE — repetitive boilerplate
Future<void> fetchProducts() async {
if (MockConfig.useMock) {
setLoading();
await MockConfig.simulateDelay();
setSuccess(data: ProductMock.list);
return;
}
await executeAsync(operation: () => baseCrudUseCase.call(...));
}
// ✅ AFTER — clean one-liner mock path
Future<void> fetchProducts() async {
await executeMockOrAsync(
mockData: ProductMock.list,
operation: () => baseCrudUseCase.call(CrudBaseParams(
api: ApiConstants.products,
httpRequestType: HttpRequestType.get,
mapper: (json) => (json['data']['data'] as List)
.map((e) => ProductEntity.fromJson(e)).toList(),
)),
);
}
Mock files location:
lib/src/core/config/mocks/{feature}_mock.dart(centralized, NOT in entity/)
@injectable
class ProductsCubit extends AsyncCubit<List<ProductEntity>> {
ProductsCubit() : super([]);
Future<void> fetchProducts() async {
await executeMockOrAsync(
mockData: ProductMock.list,
operation: () => baseCrudUseCase.call(CrudBaseParams(
api: ApiConstants.products,
httpRequestType: HttpRequestType.get,
mapper: (json) => (json['data']['data'] as List)
.map((e) => ProductEntity.fromJson(e)).toList(),
)),
);
}
}
@injectable
class ProductsCubit extends AsyncCubit<List<ProductEntity>> {
ProductsCubit() : super([]);
Future<void> fetchProducts() async {
await executeAsync(
operation: () async => baseCrudUseCase.call(CrudBaseParams(
api: ApiConstants.products,
httpRequestType: HttpRequestType.get,
mapper: (json) => (json['data']['data'] as List)
.map((e) => ProductEntity.fromJson(e)).toList(),
)),
);
}
}
NEVER re-fetch the list after add/edit/delete. Update state locally.
// Add → insert at index 0
(newItem) => setSuccess(data: [newItem, ...state.data])
// Edit → map + replace matching item
(updated) => setSuccess(data: state.data.map((e) => e.id == id ? updated : e).toList())
// Delete → removeWhere