Imported from moo-elsayed/fruit_hub_dashboard (
.agents/AGENTS.md). Install upstream withnpx skills add moo-elsayed/fruit_hub_dashboard --skill .agents. Copyright stays with the author.
Flutter Project Coding Guidelines & Best Practices
State Management & Rebuild Optimization
- Minimize
setState: UsesetStateonly when absolutely necessary and scoped to the narrowest possible widget tree to prevent unnecessary widget rebuilds. - Prefer
ValueNotifier&ValueListenableBuilder: For simple reactive UI states within widgets, useValueNotifier/ValueListenableBuilderinstead of triggering full-widgetsetState. - Reactive & Shared Cubit Instances: Share Cubit instances across child/detail routes using
BlocProvider.valueso UI updates automatically propagate in real-time across all views without redundant network re-fetching.
Component Architecture & Modularization
- No Helper Build Functions: Avoid creating private helper methods that return widgets (e.g.
_buildHeader(),_buildCard()). - Separate Custom Widgets: Always split UI sections into dedicated, reusable custom widget classes (
StatelessWidgetorStatefulWidget) placed in their own separate files underwidgets/. - Keep Files Concise & Focused: Keep screen and widget files small and readable (aim for under 150-200 lines per file). Extract buttons, forms, and calculation sections into dedicated custom widgets along with their handler logic.
Code Quality & Formatting
- Prefer Expression Bodies: Use expression function syntax
=>for concise single-statementbuild()methods, handlers, and getters. - No Hardcoded Strings: Always use a centralized strings class (e.g.
AppStrings) for all user-facing UI text, titles, hints, labels, error messages, and buttons. Hardcoded strings are strictly prohibited across UI code, except when defining mock / dummy data. - Parameter Encapsulation via Entity & Model: Whenever the number of method/function/Cubit arguments grows (e.g. 3 or more related parameters), never pass them as long loose parameter lists. Always encapsulate them into a dedicated
Entity(in Domain) and/orModel(in Data) depending on context, or create both and map between them (Model.fromEntity(entity)/model.toEntity()) to cleanly separate domain logic from data serialization.
Buttons & Action Controls
- Use a Consistent Custom Button: Never use raw
ElevatedButton,MaterialButton, or generic buttons for primary actions, forms, bottom sheets, and dialogs. Always use a shared custom button widget to guarantee uniform brand styling, loading state handling, consistent dimensions, and rounded corners.
Form & Keyboard Interactions
- Use a Consistent Custom TextField: Never use raw
TextFieldorTextFormFielddirectly. Always wrap them in a shared helper widget across all forms, dialogs, and bottom sheets to guarantee automatic bidirectional text direction (RTL/LTR), consistent theme borders, and unified styling. - Keyboard Unfocus: Always wrap screens or forms containing text input fields with a keyboard-dismissing wrapper widget so the user can easily dismiss the keyboard by tapping outside.
Theming & Color Management
- Strict Color System Usage: Never use hardcoded colors (e.g.,
Colors.white,Colors.black, raw hexColor(0xFF...)) outside the theme definition. Always access colors via the theme or a centralized color palette class.
Typography & Text Styling
- No Raw
TextStylein UI: Never instantiate rawTextStyle(...)directly inside UI widgets or screens. Always rely on a centralized typography class (e.g.AppTextStyles) or the theme'sTextTheme. For any style variations (e.g., changing color, line height, or text decoration), always call.copyWith(...)on an existing predefined typography token. This enforces consistent font families, correct font scaling, and maintainable text hierarchy across the entire application.
Routing & Navigation Guidelines
- Unified Navigation via
contextExtensions: Always use centralized navigation extensions onBuildContext. Direct use ofNavigator.push/MaterialPageRouteis strictly prohibited. - Centralized Route Registration: All views must have their route named constant in a
Routesclass and handled inside a centralAppRouter.
Layout & Spacing Optimization
- Prefer
spacingonRow,Column, &Wrap: Whenever children have uniform spacing, always use the built-inspacingproperty instead of inserting intermediateGaporSizedBoxwidgets between every child. This flattens the widget tree. - Use
GapOnly for Non-Uniform Spacing: Only resort toGap/SizedBoxwhen spacing between specific child widgets is deliberately non-uniform, asymmetric, or conditional.
Performance Best Practices
- Always enforce performance best practices (e.g., using
constconstructors where possible, avoiding heavy work insidebuildmethods, optimizing list view builders and animations).
Dependency Injection (GetIt) Guidelines
- Always Prefer
registerLazySingleton: Always usegetIt.registerLazySingletoninstead ofregisterSingletonfor all repositories, data sources, use cases, and services (unless asynchronous startup is explicitly required likeregisterSingletonAsync). This prevents unnecessary early instantiation on app startup and guarantees resources are initialized lazily on-demand.
Cubit / BLoC Design Rules
- Cubits Must Be Context-Free: Never pass
BuildContextinto a Cubit method. Cubits handle business logic only (emit state, persist data, call services). Any operation that requirescontext(e.g.context.setLocale,context.pushNamed) must live in the UI layer viaBlocListenerorBlocConsumer. - Pattern for Context-Dependent Side Effects: When a Cubit state change must trigger a context-dependent action (e.g. locale change, navigation), use a root-level
BlocListenerin the app's root widget to react to the state and call the context method there — mirroring howBlocBuilder<ThemeCubit>drivesthemeModeinMaterialApp. - Dedicated Cubit per Cross-Cutting Concern: Create a dedicated Cubit for each app-wide concern (e.g.
AppThemeCubit,AppLanguageCubit). Each Cubit owns: emitting the new state, persisting to local storage, and syncing to any remote backend. The UI layer only reacts viaBlocListener.
Services Design Guidelines
- Services Must Be Context-Free: Services must never accept or use
BuildContext. Any locale or language reading must come from a local storage service, not fromcontext.localeorPlatformDispatcher.instance.locale. - Never Read Device Language for App Language: The device system language (
PlatformDispatcher.instance.locale) and the user-selected app language (stored in preferences) are two different things. Always use the user-selected language stored in the app's own preferences service. - Constructor Injection Over
getItInside Methods: Dependencies must be injected via the class constructor, not fetched withgetIt<X>()inside methods.getItis only used at the DI registration site and at theBlocProvidercreation site. HiddengetItcalls inside service methods or Cubits are strictly prohibited. - Prefer Existing Abstractions: Before using
SharedPreferencesor any platform API directly, check whether a registered service (e.g.AppPreferencesService) already wraps it. Always use the project's own abstraction layer.
Cloud Functions (Firebase) Guidelines
- Bilingual Notification Storage: All in-app notification documents saved to Firestore must store both language variants:
titleAr,titleEn,bodyAr,bodyEn— never a singletitle/body. This allows the Flutter app to display the correct language based on the current app locale, even if the user changes language after the notification was saved. - FCM Push vs. In-App Notification Language: The FCM push banner uses the user's stored
languageCodeat the time of sending. The in-app notification screen always renders from the bilingual fields using alocalizedTitle(isArabic)/localizedBody(isArabic)helper on the entity. - Field Name Consistency: All Firestore field names used in Cloud Functions must exactly match the Dart model
toJson()/fromJson()keys. Cross-check against the model before writing a new function. languageCodeSync: ThelanguageCodefield on the user's Firestore document must be kept in sync whenever the user changes language. This sync should be triggered from the language Cubit so it's guaranteed to run on every language change.
Unit Testing Guidelines & Best Practices
- Prefer
FakeFirebaseFirestoreover Mocks: Never mock Firestore internal interfaces (MockFirebaseFirestore,MockCollectionReference,MockDocumentReference,MockDocumentSnapshot). Always useFakeFirebaseFirestore(fake_cloud_firestore) for testing Firestore-dependent data sources. This avoidssubtype_of_sealed_classanalyzer issues, eliminates complexwhen(...)stubbing, and guarantees real in-memory Firestore behavior (queries, transactions, merges, and real-time streams). - Scoped
mocktailUsage: Usemocktailexclusively for services without full in-memory fakes (e.g.FirebaseAuth,FirebaseStorage,ImageCompressor, or domain repositories when testing Cubits/Use Cases). - System Under Test (
sut) Naming: The class instance being tested must always be namedsutand instantiated insidesetUp(). - AAA (Arrange-Act-Assert) Pattern: Clearly divide every unit test into distinct
// Arrange,// Act, and// Assertphases. - Descriptive Test Naming (BDD Style): Name test cases following the format
'should [expected behavior] when [condition or scenario]'(e.g.,'should return NetworkSuccess with default model when document does not exist'). - Comprehensive Model & Entity Mappings Coverage: Every Remote Data Source test file must include a dedicated
Model and Entity Mappingsgroup verifying:toEntity()andfromEntity()mapping all properties accurately.fromJson()fallback handling on missing or null fields, default values, and numeric type coercion (inttodouble).toJson()verification of exact Firestore keys and conditional properties (e.g.,FieldValue.serverTimestamp(), optional maps).
- NetworkResponse Verifications: Always assert against the concrete
NetworkResponsesubclass:- Success:
expect(result, isA<NetworkSuccess<T>>());and verify(result as NetworkSuccess<T>).data. - Failure:
expect(result, isA<NetworkFailure<T>>());and assert on thefailure.error.
- Success:
- Merge & Real-time Stream Testing:
- When testing
SetOptions(merge: true), explicitly verify that unrelated existing fields in the document are preserved. - When testing streams, use
expectLaterwithemits(...)or aCompleterto verify emissions upon real-time updates.
- When testing
- Zero Analyzer Warnings: Test files must pass
dart analyzewith zero warnings or errors. Avoid suppressing rules (e.g.,// ignore_for_file: subtype_of_sealed_class) by relying on proper fakes.