Feature-first folder structure in Flutter
By Ann Tech · 24 November 2024
Folder structure is architecture made visible. Feature-first organization groups everything related to one feature together — making it easy to find code, delete features, and reason about boundaries.
Layer-first vs feature-first
Layer-first (common anti-pattern for large apps):
lib/
models/ # All models from all features
repositories/ # All repositories
blocs/ # All BLoCs
screens/ # All screens
widgets/ # All widgets
Problems: changing a feature means touching 5+ folders. Deleting a feature is error-prone. New developers can't tell what the app does.
Feature-first (recommended):
lib/
features/
auth/
data/
auth_repository.dart
auth_api.dart
models/
user.dart
auth_response.dart
domain/
auth_service.dart
presentation/
screens/
login_screen.dart
register_screen.dart
widgets/
auth_form.dart
bloc/
auth_bloc.dart
auth_event.dart
auth_state.dart
products/
data/
presentation/
orders/
data/
presentation/
core/
network/
theme/
utils/
app/
app.dart
router.dart
Practical structure (simpler variant)
For apps that don't need strict DDD layers:
lib/
features/
auth/
auth_repository.dart
auth_state.dart # Riverpod notifier or BLoC
login_screen.dart
register_screen.dart
auth_widgets.dart # Shared within feature
products/
product.dart # Model
products_repository.dart
products_notifier.dart
products_screen.dart
product_detail_screen.dart
product_card.dart
cart/
cart.dart
cart_notifier.dart
cart_screen.dart
orders/
order.dart
orders_repository.dart
orders_screen.dart
order_detail_screen.dart
shared/
widgets/
app_button.dart
app_text_field.dart
loading_overlay.dart
models/
pagination.dart
api_response.dart
utils/
date_utils.dart
currency_utils.dart
core/
di/
providers.dart # All Riverpod providers (or BLoC DI)
network/
dio_client.dart
auth_interceptor.dart
storage/
secure_storage.dart
app_preferences.dart
theme/
app_theme.dart
app_colors.dart
app/
app.dart
router.dart
main.dart
main_dev.dart
main_staging.dart
What goes in core/ vs shared/
core/ — infrastructure and cross-cutting concerns:
- Network client (Dio setup)
- Authentication token handling
- Dependency injection
- Logging, crash reporting
- Theme definitions
shared/ — reusable UI and domain primitives:
- Design system widgets (buttons, inputs, cards)
- Common models used across features (pagination, API error)
- Utility functions (date formatting, currency)
Feature boundaries
A feature owns its internal implementation. Other features must not import from inside a feature's internals:
// WRONG: products feature importing orders' internal model
import 'package:myapp/features/orders/order_item.dart';
// RIGHT: shared model used by both features
import 'package:myapp/shared/models/order_summary.dart';
If two features need to share a model, move it to shared/.
Barrel files
For clean imports, create a barrel file per feature:
// features/products/products.dart
export 'product.dart';
export 'products_repository.dart';
export 'products_notifier.dart';
export 'products_screen.dart';
export 'product_detail_screen.dart';
// In other files:
import 'package:myapp/features/products/products.dart';
// vs
import 'package:myapp/features/products/product.dart';
import 'package:myapp/features/products/products_repository.dart';
// ...
Scaling the structure
For very large apps, features can be extracted into separate Dart packages using Melos:
packages/
feature_auth/
lib/
test/
pubspec.yaml
feature_products/
feature_orders/
shared_ui/ # Design system components
core_network/
apps/
my_app/ # Assembles features
This enforces boundaries at the package level and enables parallel builds.
Common pitfalls
A utils/ folder that becomes a dumping ground. Anything that "doesn't fit" ends up in utils/. After 6 months, utils/ has 30 files and nobody knows what's in it. Be specific: date_utils.dart, currency_format.dart — not a catch-all helpers.dart.
Screens importing from other screens directly. If ProductDetailScreen imports from OrdersScreen, you've created a hidden coupling. Screens should only import from their own feature folder and shared/.
Reorganizing structure without team alignment. Structure changes create large git diffs that conflict with every open PR. Agree on the target structure, migrate in a dedicated PR, and merge it before other work continues.
Sign in to like, dislike, or report.