Overview
Softbee follows Clean Architecture principles with clear separation between data, domain, and presentation layers. The project is organized into core utilities and feature modules.
Root Structure
lib/
├── core/ # Shared utilities and configurations
├── feature/ # Feature modules
└── main.dart # Application entry point
Core Directory
The core/ directory contains shared code used across all features:
core/
├── error/ # Error handling and failures
├── network/ # HTTP client configuration
├── pages/ # Shared pages (landing, 404)
├── router/ # Navigation configuration
├── services/ # Cross-cutting services
├── theme/ # UI theme and colors
├── usecase/ # Base use case class
└── widgets/ # Reusable UI components
Key Core Files
lib/core/error/failures.dart
lib/core/usecase/usecase.dart
abstract class Failure {
final String message;
const Failure ( this .message);
}
class ServerFailure extends Failure {
const ServerFailure ( super .message);
}
class NetworkFailure extends Failure {
const NetworkFailure ( super .message);
}
class AuthFailure extends Failure {
const AuthFailure ( super .message);
}
Feature Modules
Each feature follows the same layered structure:
feature/
├── apiaries/ # Apiary management
├── auth/ # Authentication
├── beehive/ # Beehive tracking
├── inventory/ # Inventory management
└── monitoring/ # Hive monitoring
Clean Architecture Layers
Every feature is organized into three layers:
1. Domain Layer (Business Logic)
feature/apiaries/domain/
├── entities/ # Pure business objects
│ └── apiary.dart
├── repositories/ # Abstract repository contracts
│ └── apiary_repository.dart
└── usecases/ # Business use cases
├── get_apiaries.dart
├── create_apiary_usecase.dart
├── update_apiary_usecase.dart
└── delete_apiary_usecase.dart
Domain entities are pure Dart classes with no Flutter or external dependencies. They represent core business concepts.
2. Data Layer (Implementation)
feature/apiaries/data/
├── datasources/ # Data source implementations
│ └── apiary_remote_datasource.dart
└── repositories/ # Repository implementations
└── apiary_repository_impl.dart
Example Entity:
lib/feature/apiaries/domain/entities/apiary.dart
class Apiary {
final String id;
final String userId;
final String name;
final String ? location;
final int ? beehivesCount;
final bool treatments;
final DateTime ? createdAt;
Apiary ({
required this .id,
required this .userId,
required this .name,
this .location,
this .beehivesCount,
required this .treatments,
this .createdAt,
});
factory Apiary . fromJson ( Map < String , dynamic > json) {
return Apiary (
id : json[ 'id' ]. toString (),
userId : json[ 'user_id' ],
name : json[ 'name' ],
location : json[ 'location' ],
beehivesCount : json[ 'beehives_count' ],
treatments : json[ 'treatments' ] ?? false ,
createdAt : json[ 'created_at' ] != null
? DateFormat ( "EEE, dd MMM yyyy HH:mm:ss 'GMT'" )
. parse (json[ 'created_at' ])
: null ,
);
}
}
3. Presentation Layer (UI)
feature/apiaries/presentation/
├── controllers/ # State management controllers
│ └── apiaries_controller.dart
├── pages/ # Screen widgets
│ ├── apiary_settings_page.dart
│ ├── history_page.dart
│ └── hives_page.dart
├── providers/ # Riverpod providers
│ └── apiary_providers.dart
└── widgets/ # Feature-specific widgets
├── apiary_card.dart
└── apiary_form_dialog.dart
Feature Examples
Authentication Feature Structure
The auth feature has a slightly different structure with additional organization:
feature/auth/
├── core/ # Auth domain layer
│ ├── entities/ # User entity
│ ├── repositories/ # Auth repository contract
│ └── usecase/ # Login, register, logout use cases
├── data/ # Auth data layer
│ ├── datasources/ # Remote and local data sources
│ └── repositories/ # Repository implementation
└── presentation/ # Auth UI layer
├── controllers/ # Auth state controllers
├── pages/ # Login, register pages
├── providers/ # Auth providers
├── router/ # Auth-specific routes
└── widgets/ # Auth UI components
Dependency Flow
Dependencies flow inward: Presentation → Domain ← Data
Presentation depends on Domain
Data depends on Domain
Domain depends on nothing (pure business logic)
Main Entry Point
import 'package:flutter/material.dart' ;
import 'package:flutter_riverpod/flutter_riverpod.dart' ;
import 'core/router/app_router.dart' ;
void main () {
runApp ( const ProviderScope (child : MyApp ()));
}
class MyApp extends ConsumerWidget {
const MyApp ({ super .key});
@override
Widget build ( BuildContext context, WidgetRef ref) {
final router = ref. watch (appRouterProvider);
return MaterialApp . router (
debugShowCheckedModeBanner : false ,
routerConfig : router,
);
}
}
The app is wrapped in ProviderScope to enable Riverpod state management throughout the application.
Best Practices
Entities : entity_name.dart (e.g., apiary.dart, user.dart)
Use Cases : action_entity_usecase.dart (e.g., create_apiary_usecase.dart)
Controllers : feature_controller.dart (e.g., apiaries_controller.dart)
Pages : descriptive_page.dart (e.g., apiary_settings_page.dart)
Providers : feature_providers.dart (e.g., apiary_providers.dart)
Each feature should be as independent as possible:
Minimize cross-feature dependencies
Share code through core/ when needed
Features communicate via shared domain entities
Never import presentation code into domain
Never import data implementations into domain
Keep business logic in use cases, not controllers
Next Steps
State Management Learn how Riverpod manages application state
Routing Understand GoRouter navigation setup
Networking Explore API integration with Dio
Testing Testing strategies for Clean Architecture