Recommended Project Structure
A well-organized project structure helps maintain code quality as your application grows. Here’s the recommended structure for Juice applications, designed to scale from small to large projects.
Basic Structure
lib/
├── main.dart # App entry point
├── config/ # App configuration
│ ├── theme.dart # App theme configuration
│ ├── routes.dart # Route definitions
│ └── bloc_registry.dart # Bloc registration
├── features/ # Feature modules
│ ├── auth/ # Authentication feature
│ ├── profile/ # Profile feature
│ └── settings/ # Settings feature
├── core/ # Core application code
│ ├── services/ # Shared services
│ ├── models/ # Common data models
│ ├── widgets/ # Shared widgets
│ └── utils/ # Utility functions
└── generated/ # Generated files (localization, etc.)
Feature Module Structure
Each feature should follow this structure (using auth as an example):
features/auth/
├── auth.dart # Barrel file for the feature
├── auth_bloc.dart # Feature's bloc
├── auth_state.dart # State definition
├── auth_events.dart # Event definitions
├── models/ # Feature-specific models
│ ├── user.dart
│ └── credentials.dart
├── services/ # Feature-specific services
│ └── auth_service.dart
├── use_cases/ # Use case implementations
│ ├── login_use_case.dart
│ ├── logout_use_case.dart
│ └── register_use_case.dart
└── widgets/ # Feature UI components
├── login_form.dart
├── register_form.dart
└── auth_page.dart
Core Module Organization
The core module contains shared code used across features:
core/
├── services/ # Shared services
│ ├── api_service.dart # Network client
│ ├── storage.dart # Local storage
│ └── analytics.dart # Analytics service
├── models/ # Common data models
│ ├── result.dart # Result wrapper
│ └── error.dart # Error models
├── widgets/ # Shared widgets
│ ├── buttons/
│ │ ├── primary_button.dart
│ │ └── secondary_button.dart
│ ├── inputs/
│ │ ├── text_input.dart
│ │ └── search_bar.dart
│ └── layouts/
│ ├── responsive_layout.dart
│ └── centered_layout.dart
└── utils/ # Utility functions
├── formatters.dart # String/number formatting
├── validators.dart # Input validation
└── extensions/ # Extension methods
├── date_extensions.dart
└── string_extensions.dart
Feature Organization Best Practices
Barrel Files
Each feature should have a barrel file exporting its public API:
// auth.dart
// State and Events
export 'auth_state.dart';
export 'auth_events.dart';
export 'auth_bloc.dart';
Feature Boundaries
- Keep features self-contained
- Share code through core module
- Use barrel files to control public API
- Keep feature-specific code within feature directory
This barrel file pattern:
- Provides a single import point for counter feature
- Makes imports cleaner in other files
- Helps manage feature boundaries
- Makes refactoring easier
Things we should NOT export:
- Use cases (very rarely would you want to expore a usecase)
- Internal widgets
- Internal models
- Internal services - These should be accessed through the bloc
The key principles are:
- Export only what other features need to interact with your feature
- Keep implementation details private to the feature
- Force interaction through the bloc’s public interface
- Only expose models that are truly shared (if so, consider moving them out of the feature into a share folder)
- Expose pages needed for navigation
Service Layer Organization
Services should be organized by scope:
services/
├── core/ # Core application services
│ ├── api/ # Network services
│ │ ├── api_client.dart
│ │ └── endpoints.dart
│ ├── storage/ # Storage services
│ │ ├── secure_storage.dart
│ │ └── preferences.dart
│ └── analytics/ # Analytics services
│ └── analytics_service.dart
└── feature/ # Feature-specific services can go here or in the feature folder
├── auth/
│ └── auth_service.dart
└── profile/
└── profile_service.dart
Widget Organization
Organize shared widgets by category:
widgets/
├── buttons/ # Button components
│ ├── primary_button.dart
│ └── secondary_button.dart
├── inputs/ # Input components
│ ├── text_input.dart
│ └── search_bar.dart
├── layouts/ # Layout components
│ ├── responsive_layout.dart
│ └── centered_layout.dart
└── feedback/ # Feedback components
├── loading_indicator.dart
└── error_display.dart
Dependency Management
Configuration
config/
├── environment/ # Environment configuration
│ ├── dev.dart
│ ├── prod.dart
│ └── staging.dart
├── theme/ # Theme configuration
│ ├── colors.dart
│ └── typography.dart
└── bloc_registry.dart # Bloc registration
Dependency Resolution
// bloc_registry.dart
class BlocRegistry {
static void initialize() {
// Core blocs
BlocScope.registerFactory<AppBloc>(() => AppBloc());
// Feature blocs
BlocScope.registerFactory<AuthBloc>(() => AuthBloc(
authService: resolve<AuthService>(),
storage: resolve<StorageService>(),
));
// More bloc registrations...
}
}
Testing Structure
Mirror your lib directory structure in test:
test/
├── features/ # Feature tests
│ └── auth/
│ ├── auth_bloc_test.dart
│ ├── use_cases/
│ │ └── login_use_case_test.dart
│ └── widgets/
│ └── login_form_test.dart
├── core/ # Core module tests
│ ├── services/
│ └── widgets/
└── integration/ # Integration tests
Large Project Considerations
For larger projects, consider:
- Module-based organization:
lib/ ├── modules/ │ ├── authentication/ # Auth module │ ├── messaging/ # Messaging module │ └── payments/ # Payments module └── shared/ # Shared code
- Feature flags structure:
lib/ ├── features/ │ └── experimental/ # Experimental features └── config/ └── feature_flags.dart
- Multiple entry points: ``` lib/ ├── main_prod.dart ├── main_dev.dart └── main_