As your Flutter app grows, organizing code by type (e.g., all blocs in one folder, all pages in another) quickly becomes unmanageable. Feature-based structure – grouping all code related to a specific feature together – is a proven approach for scalability. Combined with BLoC, it keeps your codebase maintainable, testable, and easy to navigate. This guide shows you how to structure your BLoC apps by feature.
What is Feature-Based Structure?
In a feature-based structure, you organize code by features (e.g., authentication, products, cart) rather than by technical layers (e.g., blocs, models, views). Each feature contains everything it needs: UI, business logic, data models, and repositories. This approach has several advantages:
- Cohesion – Related code is kept together.
- Scalability – Adding a new feature doesn't clutter existing ones.
- Team collaboration – Different teams can work on separate features without merge conflicts.
- Lazy loading – Features can be loaded on demand.
- Testability – Features can be tested in isolation.
Recommended Folder Structure
Feature Example: Authentication
Let’s look at the auth feature in detail. Each subfolder has a specific responsibility:
- pages/ – Screens (LoginPage, RegisterPage).
- widgets/ – Reusable UI components (LoginForm, SocialButtons).
- bloc/ – AuthBloc (events, states, bloc).
Dependency Injection Across Features
Use get_it to register dependencies at the app level. This allows you to wire up features and share instances (like Dio) across features. You can register each feature's dependencies in its own init function and call them in main.
Routing with Features
For navigation, you can use go_router with feature-based routes. Each feature can export its own GoRoute definition, and you compose them in a central router. This keeps routing modular.
Best Practices
- Keep features independent – Avoid importing one feature into another. Use shared core services for communication.
- Use a
corefolder for truly shared code – Utils, constants, widgets, and error handling that multiple features need. - Define clear boundaries – Each feature should have a single responsibility (e.g., authentication, product management).
- Use dependency injection – Don't instantiate dependencies inside features; inject them to keep code testable.
- Export feature's public API – If you plan to split features into packages later, create a public API for each feature (e.g.,
auth_api.dart). - Keep folders shallow – Avoid nesting more than 3‑4 levels deep.
Common Mistakes
- ❌ Creating circular dependencies between features – Feature A imports Feature B and vice versa. ✅ Use shared services or events to communicate.
- ❌ Putting all code in one giant feature – Defeats the purpose. ✅ Split into smaller, focused features.
- ❌ Mixing UI and business logic across features – Makes testing hard. ✅ Keep each feature self‑contained.
- ❌ Hardcoding imports with deep relative paths – Hard to refactor.
✅ Use
package:imports (after structuring your project). - ❌ Forgetting to register dependencies per feature – May cause missing dependencies at runtime.
✅ Call each feature's
initfunction inmain.
Conclusion
A feature-based structure combined with BLoC gives you a scalable, maintainable architecture for large Flutter apps. By keeping each feature self‑contained and using dependency injection, you enable parallel development, easier testing, and a clear separation of concerns. Start with a simple folder layout and evolve it as your app grows.