Technology Stack
- Flutter for cross-platform development
- Dart as the primary programming language
- bloc for state management
- injectable for dependency injection
- Dart Mappable for immutable data models
- Dio for HTTP networking
- isar for local database
- Firebase for backend services
Clean Architecture
- Domain Purity: The
domainlayer must be pure Dart. NOpackage:flutterimports. - Layer Dependency:
Presentation -> Domain <- Data. Data layer implements Domain interfaces. - Feature-First 2.0: Enforce strict separation of
DataSources(External/Raw) vsRepositories(Domain abstraction).
Directory Structure
- Organize code according to feature-based Clean Architecture pattern (
featureA/bloc,featureA/models,featureA/views) - Place cross-feature components in the
coredirectory - Group shared widgets in
core/views/widgetsby type
Three-Layer Data Model Pattern
-
API Layer (ItemApiModel)
- Represents the raw data structure as received from the server
- Contains all fields provided by the API
- Should match the API contract exactly
-
Domain Layer (Item)
- Represents the internal data model
- Contains only the fields necessary for business logic
- Strips out unnecessary API fields
-
UI Layer (ItemUiState)
- Represents the data model optimized for UI rendering
- Contains parsed and formatted data ready for display
- Handles all UI-specific transformations
Data Model Rules
- Use Dart Mappable for defining immutable UI states
- Each layer should have its own type definition
- The UI layer should use the UI state data models, never directly the domain model or the API model
- The UI state model should be derived from the domain model, not the API model
- The domain model should be derived from the API model, not the UI state model
Repository & DataSource Pattern
- Repositories orchestrate between data sources (return Domain Model)
- Data sources access raw data (return API Model)
- The repository should be the source of truth, and it returns the domain model
- DataSources MUST only contain raw SDK/API calls. No mapping or business logic.
- No direct backend SDK/API calls outside DataSources
Data Flow
UI Event β BLoC (emit Loading) β Repository β DataSource (API/SDK)
β
Response β Repository (map to Domain Entity) β BLoC (emit Success/Error) β UI
Dart 3 Language Features
- Sealed Classes: Use
sealed classfor domain failures to enable exhaustive pattern matching across layers. - Records: Use records for lightweight multi-value returns where defining a full class is overkill (e.g.,
(String name, int age)). - If-Case Pattern: Prefer
if (value case final v?)overif (value != null)for null checking with binding. - Class Modifiers: Use
final,interface,base, andsealedclass modifiers to express API intent.
Error Handling
- Functional Error Handling: Use
Either<Failure, T>orResult<T>sealed classes. NEVER throw exceptions across layer boundaries. - Pattern Matching: Exhaustively handle all sealed class states using Dart 3.x
switchexpressions in UI and BLoCs. - Throw errors when needed, and catch them at appropriate boundaries
- Log errors with context
- Present user-friendly error messages in the UI
- Avoid silent failures; always handle or propagate errors
Platform Channels & Native Integration
- Use
MethodChannelfor one-off calls to native code (e.g., reading device info, triggering native APIs) - Use
EventChannelfor continuous streams from native to Flutter (e.g., sensor data, connectivity changes) - Place all channel code in a dedicated
platform/directory within the relevant feature - Define channel names as constants:
static const channel = MethodChannel('com.app.feature/method') - Wrap all channel calls in a DataSource β never call
MethodChanneldirectly from BLoC or UI - Handle
MissingPluginExceptiongracefully for platforms that donβt implement the channel - Use
defaultTargetPlatformchecks to guard platform-specific behavior
FFI (Foreign Function Interface)
- Use
dart:ffifor performance-critical native C/C++ code - Define bindings in a separate class with clear documentation
- Prefer Federated Plugins when sharing native code across multiple packages
Platform-Specific Code
- Use
Platform.isAndroid/Platform.isIOSfor runtime checks (importdart:io) - For web, use
kIsWebfrompackage:flutter/foundation.dart - Prefer adaptive widgets (
Switch.adaptive,Slider.adaptive) over manual platform checks where possible
Coding Guidelines & Maintenance
- Conciseness: Keep files < 300 lines and functions < 50 lines. Keep classes to < 10 public methods.
- Strong Typing: STRICTLY prohibit
dynamic. UseObject?or explicit types. - Guard Clauses: Use early returns (e.g.,
if (user == null) return;) to reduce nesting and improve readability. - Disposable Lifecycle:
TextEditingController,ScrollController,FocusNode,StreamSubscription,AnimationController, etc., MUST belateinitialized ininitState()and disposed indispose(). - No Print Statements: STRICTLY prohibit
print(). UseAppLoggerfor all logging. - Reuse: Extract widgets or code blocks used multiple times into
core/views/widgetsor utilities.
Documentation
- Why, not What: Comments MUST explain the rationale (intent), not what the code does.
- Public API: Document public classes and methods with triple-slash (
///) comments. - History: Do NOT include version history or βfixed byβ comments. Git is the source of truth.