Project Structure
FFCA leans heavily on convention. Package names, folder names, and the direction dependencies are allowed to flow are all fixed. Consistent structure like this enables tooling inference, AI comprehension, and mechanical validation, so you can check the shape of a project with a script rather than in code review.
Dependency rules
Section titled “Dependency rules”The order of dependencies is as follows:
- Apps depend on features and shared packages.
- Shared packages may not depend on anything other than external packages.
- Features depend on shared libraries and other feature domains.
- The domain layer does not import anything else from the feature.
- The data layer imports the domain layer, so it can implement the repository interface.
- The presentation layer imports the domain layer, and the app wires in the data layer implementation.
flowchart TB
subgraph Domain
Models["Models"]
RepoInterface["Repository Interface"]
UseCases["Commands and Queries (optional)"]
UseCases -->|"maps to"| Models
UseCases --> RepoInterface
end
subgraph Data
DTO["DTO"]
DataSources["Data Sources"]
RepoImpl["Repository Impl"]
Mappers["Mappers"]
RepoImpl --> DataSources
RepoImpl --> Mappers
DataSources -->|"uses"| DTO
end
subgraph Presentation
BLoC["Bloc / Cubit"]
Widget["Widget / View"]
Widget --> BLoC
end
Presentation --> Domain
BLoC --> UseCases
RepoImpl -.->|"implements"| RepoInterface
RepoInterface -->|"uses"| Models
Mappers <-->|"maps to/from"| Models
Mappers --> DTO
Naming conventions
Section titled “Naming conventions”The architecture enforces naming conventions for packages. This is how tooling and AI agents can work out what a package does without opening it.
| Package | Convention | Example |
|---|---|---|
| Feature domain | {feature}_domain | cart_domain |
| Feature data | {feature}_data | cart_data |
| Feature presentation | {feature}_presentation | cart_presentation |
| Data with specific backend | {feature}_data_{backend} | auth_data_firebase |
| Shared package | descriptive name | ui_kit, api_client |
Data packages with a backend suffix, such as auth_data_firebase, signal the backing implementation. Swapping to auth_data_auth0 requires no structural changes anywhere else, because everything upstream depends on auth_domain.
Folder layout
Section titled “Folder layout”Directoryapps/
Directorykiosk_app/ Flutter package
- …
Directorymobile_app/ Flutter package
- …
Directoryadmin_app/ Flutter package
- …
Directoryfeatures/
Directoryproduct/
Directoryproduct_domain/ Dart package
Directorylib/
Directorymodels/
- product.dart
Directoryrepositories/
- i_products_repository.dart
- product_domain.dart barrel file
Directoryproduct_data/ Dart package
Directorylib/
Directorydata_sources/
Directoryproducts_remote_data_source/
Directorydtos/
- product_dto.dart
- products_remote_data_source.dart
Directorymappers/
- product_mapper.dart
Directoryrepositories/
- products_repository.dart
- product_data.dart barrel file
Directoryproduct_presentation/ Flutter package
Directorylib/
Directoryproduct_detail/
Directorybloc/
- …
Directoryviews/
- …
- product_detail_module.dart
Directoryproduct_list/
Directorybloc/
- …
Directoryviews/
- …
- product_list_module.dart
- product_detail.dart subfeature barrel
- product_list.dart subfeature barrel
- product_presentation.dart primary barrel
Directorycart/ depends on product_domain
Directorycart_domain/
- …
Directorycart_data/
- …
Directorycart_presentation/
- …
Directoryauth/ headless feature, no presentation
Directoryauth_domain/
- …
Directoryauth_data_firebase/
- …
Directoryanalytics/ headless feature
Directoryanalytics_domain/
- …
Directoryanalytics_data_posthog/
- …
Directoryuser_profile/ headless feature
Directoryuser_profile_domain/
- …
Directoryuser_profile_data/
- …
Directoryshared/
Directoryapi_client/
- …
Directoryui_kit/
- …
Directorylocalizations/
- …
Directorylogging/ headless feature, generic enough to share
Directorylogging_domain/
- …
Directorylogging_data_sentry/
- …
Layer subfolders
Section titled “Layer subfolders”Each layer uses the same subfolders every time, so you always know where to look for something.
Domain ({feature}_domain/lib/):
| Folder | Contents |
|---|---|
models/ | Domain models, as pure Dart classes with value equality |
repositories/ | Repository interfaces, declared as abstract interface class |
use_cases/ | Command and Query classes. Only needed when combining multiple repositories |
One note on naming: the folder keeps the conventional use_cases/ name so the layout matches other clean architecture projects, even though we name the classes themselves Command and Query.
Data ({feature}_data/lib/):
| Folder | Contents |
|---|---|
data_sources/ | Remote and local data sources, each with a dtos/ subfolder |
mappers/ | Extension methods mapping DTOs and generated classes to domain models |
repositories/ | Concrete repository implementations |
Presentation ({feature}_presentation/lib/):
| Folder | Contents |
|---|---|
{screen_name}/bloc/ | Bloc or Cubit, plus state and event classes |
{screen_name}/views/ | Screen and widget implementations |
{screen_name}/{screen_name}_module.dart | The module wiring dependencies for that screen |
Each layer has a primary barrel file at the root of lib/, named {feature}_{layer}.dart, plus subfeature barrel files for any entry point that should be independently importable.
Monorepo tooling
Section titled “Monorepo tooling”Dart workspaces
Section titled “Dart workspaces”Utilize Dart workspaces to manage the monorepo. This ensures all packages within the project utilize the same versions of external packages. If conflicts appear with a transitive dependency, use Dart’s tooling to identify and resolve the issue.
Running commands across packages
Section titled “Running commands across packages”There are two options here. Choose whichever one makes most sense for your project or scenario:
- Melos (recommended): the most widely used tool for Flutter and Dart monorepos. It handles dependency management, runs scripts across all packages, supports filtering by Dart or Flutter packages, and automates versioning and changelog generation. It works on all platforms.
- A
toolfolder: if you have more intricate actions to perform, such as fetching localizations from a service for multiple feature packages, consider writing a Dart program inside atoolfolder. See Dart’s package layout conventions page for more information.
Add-to-app support
Section titled “Add-to-app support”FFCA’s feature isolation maps naturally to Flutter’s add-to-app pattern. Each feature’s module is a self-contained entry point with explicit dependencies, making it embeddable in a native host app.
The module takes its dependencies as constructor parameters, wires its own provider tree, and communicates outward through navigation callbacks. In a full FFCA app, the app layer instantiates the module inside a GoRouteData.build(). In add-to-app, the module is instantiated directly as the root widget of a FlutterEngine. The module code is identical. The only difference is what the callbacks target, go_router routes or platform channels.
@pragma('vm:entry-point')void cartEntryPoint(String cartId) { final apiClient = ApiClient(baseUrl: 'https://api.example.com'); final cartsRepository = CartsRepository(apiClient: apiClient); final productsRepository = ProductsRepository(apiClient: apiClient);
const channel = MethodChannel('com.app/navigation');
runApp( MaterialApp( home: CartListModule( id: cartId, cartsRepository: cartsRepository, productsRepository: productsRepository, // Hand control back to native through a platform channel. onProductTapped: (productId) => channel.invokeMethod('showProduct', {'id': productId}), onCheckoutStarted: () => channel.invokeMethod('showCheckout'), ), ), );}Subfeature barrels enable granular embedding. A native app can embed just one screen from a feature, via its subfeature barrel, without pulling in the entire feature, minimizing Flutter binary size.