What are the different types of architecture in flutter and benefits (feature first , layer first)
Answer
Overview
Flutter architecture patterns organize code into structured layers. The two main approaches are feature-first (organized by features) and layer-first (organized by technical layers).
Feature-First Architecture
Code is organized by features/modules (also called "package by feature").
Structure
textlib/ features/ authentication/ data/ models/ user_model.dart repositories/ auth_repository_impl.dart datasources/ auth_remote_datasource.dart domain/ entities/ user.dart repositories/ auth_repository.dart usecases/ login_usecase.dart presentation/ pages/ login_page.dart widgets/ login_form.dart bloc/ auth_bloc.dart home/ data/ domain/ presentation/ profile/ data/ domain/ presentation/ core/ utils/ constants/ widgets/ main.dart
Benefits
| Benefit | Description |
|---|---|
| High cohesion | Related code stays together |
| Easy navigation | Find everything for a feature in one place |
| Scalability | Add features without affecting others |
| Team collaboration | Teams own specific features |
| Modularity | Features can become packages |
Example
Authentication feature:
dart// features/authentication/domain/entities/user.dart class User { final String id; final String email; User({required this.id, required this.email}); } // features/authentication/domain/usecases/login_usecase.dart class LoginUseCase { final AuthRepository repository; LoginUseCase(this.repository); Future<User> call(String email, String password) { return repository.login(email, password); } } // features/authentication/presentation/bloc/auth_bloc.dart class AuthBloc extends Bloc<AuthEvent, AuthState> { final LoginUseCase loginUseCase; AuthBloc(this.loginUseCase) : super(AuthInitial()) { on<LoginRequested>(_onLoginRequested); } Future<void> _onLoginRequested( LoginRequested event, Emitter<AuthState> emit, ) async { emit(AuthLoading()); try { final user = await loginUseCase(event.email, event.password); emit(AuthSuccess(user)); } catch (e) { emit(AuthError(e.toString())); } } }
Layer-First Architecture
Code is organized by technical layers (also called "package by layer").
Structure
textlib/ data/ models/ user_model.dart product_model.dart repositories/ auth_repository_impl.dart product_repository_impl.dart datasources/ remote/ auth_api.dart product_api.dart local/ database.dart domain/ entities/ user.dart product.dart repositories/ auth_repository.dart product_repository.dart usecases/ login_usecase.dart fetch_products_usecase.dart presentation/ pages/ login_page.dart home_page.dart product_page.dart widgets/ custom_button.dart product_card.dart bloc/ auth_bloc.dart product_bloc.dart core/ utils/ constants/ main.dart
Benefits
| Benefit | Description |
|---|---|
| Clear separation | Technical layers are distinct |
| Easy to understand | Follows traditional MVC/MVVM patterns |
| Good for small apps | Simpler structure for few features |
| Onboarding | New developers understand layers quickly |
Example
Layer-first authentication:
dart// domain/entities/user.dart class User { final String id; final String email; User({required this.id, required this.email}); } // domain/repositories/auth_repository.dart abstract class AuthRepository { Future<User> login(String email, String password); } // data/repositories/auth_repository_impl.dart class AuthRepositoryImpl implements AuthRepository { final AuthApi authApi; AuthRepositoryImpl(this.authApi); Future<User> login(String email, String password) async { final userModel = await authApi.login(email, password); return User(id: userModel.id, email: userModel.email); } } // presentation/bloc/auth_bloc.dart class AuthBloc extends Bloc<AuthEvent, AuthState> { final AuthRepository authRepository; // ... (same as feature-first example) }
Comparison
| Aspect | Feature-First | Layer-First |
|---|---|---|
| Organization | By feature/module | By technical layer |
| Scalability | ✅ Excellent | ⚠️ Moderate |
| Navigation | ✅ Easy (feature folder) | ⚠️ Harder (scattered files) |
| Team collaboration | ✅ Teams own features | ⚠️ Conflicts on shared layers |
| Best for | Large apps, microservices | Small to medium apps |
| Onboarding | ⚠️ Requires explanation | ✅ Familiar to most devs |
| Code reuse | ⚠️ Cross-feature imports | ✅ Centralized layers |
When to Use Which
Use Feature-First
- ✅ Large apps (10+ features)
- ✅ Multiple teams working in parallel
- ✅ Features can be extracted as packages
- ✅ Microservices-style architecture
- ✅ Long-term scalability is priority
Example apps:
- E-commerce (authentication, cart, checkout, orders, profile)
- Social media (feed, messaging, profile, notifications)
Use Layer-First
- ✅ Small to medium apps (< 10 features)
- ✅ Single team
- ✅ Simple domain logic
- ✅ Quick prototyping
- ✅ Learning Flutter architecture
Example apps:
- Todo app
- Note-taking app
- Simple CRUD apps
Hybrid Approach
Combine both for best of both worlds.
textlib/ features/ authentication/ # Feature-first data/ domain/ presentation/ home/ data/ domain/ presentation/ shared/ # Shared layer-first data/ models/ repositories/ presentation/ widgets/ theme/ core/ # Core utilities utils/ constants/ main.dart
Clean Architecture (Feature-First)
Most popular approach for large Flutter apps.
Layers
textPresentation Layer (UI) ↓ Domain Layer (Business Logic) ↓ Data Layer (API, Database)
Dependency Rule
Outer layers depend on inner layers, never the reverse.
dart// ✅ Presentation depends on Domain class AuthBloc { final LoginUseCase loginUseCase; // Domain layer } // ✅ Data depends on Domain class AuthRepositoryImpl implements AuthRepository { // Implements domain repository interface } // ❌ Domain should NOT depend on Data or Presentation
Complete Feature-First Example
features/products/domain/entities/product.dart:
dartclass Product { final String id; final String name; final double price; Product({required this.id, required this.name, required this.price}); }
features/products/domain/repositories/product_repository.dart:
dartabstract class ProductRepository { Future<List<Product>> fetchProducts(); }
features/products/data/repositories/product_repository_impl.dart:
dartclass ProductRepositoryImpl implements ProductRepository { final ProductApi productApi; ProductRepositoryImpl(this.productApi); Future<List<Product>> fetchProducts() async { final models = await productApi.getProducts(); return models.map((m) => Product(id: m.id, name: m.name, price: m.price)).toList(); } }
features/products/presentation/bloc/product_bloc.dart:
dartclass ProductBloc extends Bloc<ProductEvent, ProductState> { final ProductRepository productRepository; ProductBloc(this.productRepository) : super(ProductInitial()) { on<FetchProducts>(_onFetchProducts); } Future<void> _onFetchProducts( FetchProducts event, Emitter<ProductState> emit, ) async { emit(ProductLoading()); try { final products = await productRepository.fetchProducts(); emit(ProductLoaded(products)); } catch (e) { emit(ProductError(e.toString())); } } }
Best Practices
dart// ✅ Keep features independent // features/authentication/ should NOT import from features/products/ // ✅ Use shared code for cross-feature utilities // shared/widgets/custom_button.dart (used by multiple features) // ✅ Follow dependency rule (Clean Architecture) // Presentation → Domain → Data // ❌ Don't mix architectures inconsistently // Pick one and stick to it throughout the app
Summary
| Architecture | Structure | Best For |
|---|---|---|
| Feature-First | By feature/module | Large apps, teams |
| Layer-First | By technical layer | Small apps, solo devs |
| Hybrid | Features + shared layers | Medium to large apps |
| Clean Architecture | Layered + feature-first | Enterprise apps |
Recommendation: Start with layer-first for small apps, migrate to feature-first as the app grows.
Learn more: