Flutter App Architecture: Feature-Based vs. Layer-Based Folder Structure

Flutter Coding Hub
5 min read5 days ago

--

When building Flutter applications, choosing between feature-based and layer-based folder structures impacts maintainability and scalability. This guide compares both Flutter app architecture approaches, helping developers organize their code efficiently for better project management.Layer-Based Architecture

In a layer-based (or horizontal) architecture, the codebase is organized according to technical roles or responsibilities, with each layer serving a specific purpose in the application.

Typical Layer-Based Structure

lib/
├── api/ // API client, endpoints, network-related code
├── blocs/ // Business Logic Components (or providers/controllers)
├── models/ // Data models/entities
├── repositories/ // Data sources and repositories
├── utils/ // Helper functions and utilities
├── screens/ // UI screens/pages
├── widgets/ // Reusable UI components
├── constants/ // App-wide constants
└── main.dart // Entry point

Advantages of Layer-Based Architecture

  1. Familiar pattern — Many developers find this intuitive as it follows traditional MVC/MVVM patterns.
  2. Clear technical separation — Clean boundaries between data, business logic, and UI.
  3. Easier to find all widgets — All UI components are in one place.
  4. Simpler for small projects — Works well when the app has a limited scope.

Disadvantages of Layer-Based Architecture

  1. Feature fragmentation — Code for a single feature is spread across multiple directories.
  2. Navigation complexity — Developers need to jump between folders frequently when working on a feature.
  3. Scaling challenges — As the app grows, folders like screens/ or blocs/ become crowded.
  4. Harder to isolate or modularize features — Making a feature reusable or optional becomes challenging.

Feature-Based Architecture

In a feature-based (or vertical) architecture, the codebase is organized around business features or user-facing functionality, with each feature containing all the necessary components.

Typical Feature-Based Structure

lib/
├── core/ // Core utilities and shared functionality
│ ├── network/ // Base network client
│ ├── storage/ // Local storage helpers
│ ├── widgets/ // Common widgets used across features
│ └── constants/ // App-wide constants
├── features/ // All app features
│ ├── authentication/
│ │ ├── data/ // Models, repositories, API services
│ │ ├── domain/ // Business logic (blocs, providers)
│ │ └── presentation/ // UI (screens, widgets)
│ ├── home/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ └── settings/
│ ├── data/
│ ├── domain/
│ └── presentation/
└── main.dart // Entry point

Advantages of Feature-Based Architecture

  1. Cohesive units — All code related to a feature is grouped together.
  2. Improved development experience — Less context switching when working on a specific feature.
  3. Better scalability — Easy to add new features without cluttering existing folders.
  4. Supports modularity — Features can be extracted, reused, or made optional.
  5. Team collaboration — Different teams can work on different features with minimal conflicts.

Disadvantages of Feature-Based Architecture

  1. Initial complexity — May seem more complex to set up at first.
  2. Potential code duplication — Similar components might be duplicated across features.
  3. Finding common widgets — Can be harder to locate shared components.
  4. Learning curve — New team members might take time to understand where everything is.

Combining the Best of Both Approaches

Many successful Flutter projects adopt a hybrid approach that leverages the advantages of both patterns:

lib/
├── core/ // Core functionality, shared across features
│ ├── network/ // Base network client
│ ├── storage/ // Storage utilities
│ ├── theme/ // Theme data
│ ├── widgets/ // Common widgets
│ └── utils/ // Utilities and helpers
├── features/ // Feature modules
│ ├── authentication/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ ├── home/
│ ├── profile/
│ └── settings/
└── main.dart // Entry point

This structure uses feature-based organization for application features while maintaining a layer-based approach within each feature.

Practical Implementation Details

Feature-Based Implementation

Within each feature directory, you can implement a Clean Architecture or similar pattern:

features/authentication/
├── data/
│ ├── datasources/ // API clients, local storage
│ ├── models/ // DTOs, serialization
│ └── repositories/ // Repository implementations
├── domain/
│ ├── entities/ // Business models
│ ├── repositories/ // Repository interfaces
│ ├── usecases/ // Business logic
│ └── blocs/ // State management
└── presentation/
├── pages/ // Screens
├── widgets/ // Feature-specific widgets
└── navigation/ // Routes specific to this feature

When implementing the feature-based approach, each feature might have:

  1. A public API (main entry points and models exposed to other features)
  2. Private implementations that other features cannot directly access
  3. Feature-specific routing that can be integrated into the main app router

Layer-Based Implementation

In a layer-based approach, you might structure repositories and services with clearer organization:

repositories/
├── auth_repository.dart
├── user_repository.dart
├── settings_repository.dart
└── repository_base.dart
services/
├── api/
│ ├── auth_api.dart
│ ├── user_api.dart
│ └── api_client.dart
└── storage/
├── local_storage.dart
└── secure_storage.dart

Decision Factors

Consider these factors when choosing between approaches:

  1. Team size: Larger teams often benefit from feature-based architecture
  2. App complexity: More complex apps usually need better feature isolation
  3. Development experience: What structure will make developers most productive
  4. Maintainability goals: How you plan to maintain the app long-term
  5. Feature stability: How frequently features change or get added

Real-World Examples

Feature-Based Example: E-Commerce App

lib/
├── core/
│ ├── theme/
│ ├── network/
│ └── widgets/
├── features/
│ ├── auth/
│ ├── product_catalog/
│ ├── shopping_cart/
│ ├── checkout/
│ ├── orders/
│ └── user_profile/
└── main.dart

This structure makes it easy to understand what the app does just by looking at the feature list, and each team can focus on specific features.

Layer-Based Example: Simple To-Do App

lib/
├── models/
│ ├── task.dart
│ └── user.dart
├── repositories/
│ ├── task_repository.dart
│ └── user_repository.dart
├── blocs/
│ ├── task_bloc.dart
│ └── auth_bloc.dart
├── screens/
│ ├── home_screen.dart
│ ├── task_detail_screen.dart
│ └── login_screen.dart
├── widgets/
│ ├── task_item.dart
│ └── custom_button.dart
└── main.dart

For a simpler app, this straightforward approach might be sufficient and easier to understand.

Conclusion

Both feature-based and layer-based architectures have their place in Flutter development:

  • Layer-based works well for smaller apps with a limited scope or when the team is more familiar with traditional architectures.
  • Feature-based excels for larger applications, teams working in parallel, or when you need strong modularization.
  • Hybrid approaches often provide the best of both worlds, using feature-based organization at the top level while maintaining layer-based structure within each feature.

The best approach depends on your specific project requirements, team preferences, and long-term maintenance goals. As your app grows, you might even find yourself gradually migrating from a layer-based to a more feature-based structure.

Would you like me to elaborate on any specific aspect of these architectural approaches? Or perhaps discuss how to implement them with specific state management solutions like BLoC, Provider, or Riverpod?

This article was published on fluttercodinghub.com a community resource dedicated to Flutter development best practices and optimization techniques.

--

--

No responses yet