Skip to content

Latest commit

 

History

History
796 lines (654 loc) · 17.7 KB

File metadata and controls

796 lines (654 loc) · 17.7 KB

Flutter Architecture Patterns - Feature Comparison

📊 Quick Comparison Table

Feature MVC MVVM Clean Architecture DDD
Layers 3 (M-V-C) 3 (M-V-VM) 3 (Data-Domain-Presentation) 4 (Domain-Application-Infrastructure-Presentation)
Complexity ⭐ Simple ⭐⭐ Moderate ⭐⭐⭐ Complex ⭐⭐⭐⭐ Most Complex
Learning Curve Easy Easy-Moderate Moderate-Hard Hard
Code Lines ~800 ~1,000 ~1,500 ~2,500
Files Count ~10 ~15 ~25 ~40
Home View ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Counter Feature ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Notes Feature ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Theme Toggle ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Persistence ✅ Shared Prefs ✅ Hydrated BLoC ✅ Hydrated BLoC ✅ Hydrated BLoC
State Management BLoC/Cubit BLoC/Cubit BLoC/Cubit BLoC/Cubit
Testability ⭐⭐ Moderate ⭐⭐⭐ Good ⭐⭐⭐⭐ Excellent ⭐⭐⭐⭐⭐ Best
Scalability ⭐⭐ Small-Medium ⭐⭐⭐ Medium-Large ⭐⭐⭐⭐ Large ⭐⭐⭐⭐⭐ Enterprise
Best For Small apps, prototypes Medium apps, UI-heavy Large apps, teams Enterprise, complex domains

🏗️ Architecture Layers Comparison

MVC (Model-View-Controller)

View (UI)
  ↓
Controller (Logic)
  ↓
Model (Data)

Advantages:

  • ✅ Simple and straightforward
  • ✅ Easy to learn and understand
  • ✅ Quick to implement
  • ✅ Less boilerplate code

Disadvantages:

  • ❌ Controllers can become bloated (Fat Controller problem)
  • ❌ Tight coupling between layers
  • ❌ Harder to test in isolation
  • ❌ Not ideal for complex apps

MVVM (Model-View-ViewModel)

View (UI)
  ↓↑ (Two-way binding)
ViewModel (Presentation Logic)
  ↓
Model (Data)

Advantages:

  • ✅ Clear separation of UI and logic
  • ✅ ViewModels are highly testable
  • ✅ Reactive data binding
  • ✅ Better than MVC for complex UI

Disadvantages:

  • ❌ Can be overkill for simple apps
  • ❌ ViewModels can become complex
  • ❌ More files and boilerplate than MVC
  • ❌ Learning curve for reactive programming

Clean Architecture

Presentation (UI, Controllers)
       ↓
   Domain (Use Cases, Entities)
       ↓
    Data (Repositories, Data Sources)

Advantages:

  • ✅ Framework-independent domain layer
  • ✅ Highly testable at all layers
  • ✅ Clear dependency rules
  • ✅ Excellent for large teams
  • ✅ Easy to swap implementations

Disadvantages:

  • ❌ More complex than MVC/MVVM
  • ❌ More files and folders
  • ❌ Overhead for small projects
  • ❌ Steeper learning curve

DDD (Domain-Driven Design)

Presentation (UI, Controllers, Bindings)
       ↓
Infrastructure (DTOs, DataSources, Repo Impl)
       ↓
Application (Use Cases)
       ↓
   Domain (Entities, Value Objects, Repo Interfaces)

Advantages:

  • ✅ Pure domain layer (zero dependencies)
  • ✅ Rich domain models with behavior
  • ✅ Value objects with validation
  • ✅ Perfect for complex business logic
  • ✅ Bounded contexts for large systems
  • ✅ Best testability

Disadvantages:

  • ❌ Most complex architecture
  • ❌ Highest file count
  • ❌ Longest learning curve
  • ❌ Overkill for simple apps
  • ❌ More initial development time

💻 Code Structure Comparison

MVC Structure

mvc_architeture_pattern/
├── lib/
│   ├── models/
│   │   ├── counter_model.dart
│   │   └── note_model.dart
│   ├── views/
│   │   ├── home_view.dart
│   │   ├── counter_view.dart
│   │   └── notes_view.dart
│   ├── controllers/
│   │   ├── counter_controller.dart
│   │   ├── notes_controller.dart
│   │   └── theme_controller.dart
│   └── main.dart

MVVM Structure

mvvm_architeture_pattern/
├── lib/
│   ├── models/
│   │   ├── counter_model.dart
│   │   └── note_model.dart
│   ├── views/
│   │   ├── home_view.dart
│   │   ├── counter_view.dart
│   │   └── notes_view.dart
│   ├── viewmodels/
│   │   ├── counter_viewmodel.dart
│   │   └── notes_viewmodel.dart
│   ├── bindings/
│   ├── routes/
│   ├── config/
│   └── main.dart

Clean Architecture Structure

clean_architeture_pattern/
├── lib/
│   ├── core/
│   │   ├── routes/
│   │   ├── theme/
│   │   ├── utils/
│   │   └── presentation/views/
│   ├── features/
│   │   ├── counter/
│   │   │   ├── data/
│   │   │   ├── domain/
│   │   │   └── presentation/
│   │   └── notes/
│   │       ├── data/
│   │       ├── domain/
│   │       └── presentation/
│   └── main.dart

DDD Structure

ddd_architeture_pattern/
├── lib/
│   ├── domain/
│   │   ├── core/
│   │   ├── counter/
│   │   │   ├── entities/
│   │   │   ├── value_objects/
│   │   │   └── repositories/
│   │   └── notes/
│   ├── application/
│   │   ├── counter/usecases/
│   │   └── notes/usecases/
│   ├── infrastructure/
│   │   ├── counter/
│   │   │   ├── dtos/
│   │   │   ├── datasources/
│   │   │   └── repositories/
│   │   └── notes/
│   ├── presentation/
│   │   ├── core/
│   │   ├── counter/
│   │   └── notes/
│   └── main.dart

🎯 Feature Implementation Comparison

Counter Feature

MVC Implementation

// Model
class CounterModel {
  int value = 0;
}

// Controller (using Cubit)
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterInitial());
  
  final model = CounterModel();
  
  void increment() {
    model.value++;
    emit(CounterUpdated(model.value));
  }
  
  void decrement() {
    model.value--;
    emit(CounterUpdated(model.value));
  }
  
  void reset() {
    model.value = 0;
    emit(CounterUpdated(model.value));
  }
}

// View
BlocBuilder<CounterCubit, CounterState>(
  builder: (context, state) {
    if (state is CounterUpdated) {
      return Text('${state.value}');
    }
    return Text('0');
  },
)

MVVM Implementation

// Model
class CounterModel {
  final int value;
  CounterModel(this.value);
}

// ViewModel (using Cubit)
class CounterViewModel extends Cubit<int> {
  CounterViewModel() : super(0);
  
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

// View
BlocBuilder<CounterViewModel, int>(
  builder: (context, count) => Text('$count'),
)

Clean Architecture Implementation

// Entity (Domain)
class Counter {
  final int value;
  Counter(this.value);
}

// Use Case (Domain)
class GetCounterUseCase {
  Future<Counter> execute() async {
    return await repository.getCounter();
  }
}

// Repository Interface (Domain)
abstract class CounterRepository {
  Future<Counter> getCounter();
}

// Repository Implementation (Data)
class CounterRepositoryImpl implements CounterRepository {
  @override
  Future<Counter> getCounter() async {
    // Implementation
  }
}

// Controller (Presentation)
class CounterController extends GetxController {
  final GetCounterUseCase getCounterUseCase;
  
  void increment() async {
    await incrementCounterUseCase.execute();
  }
}

DDD Implementation

// Value Object (Domain)
class CounterValue extends Equatable {
  final int number;
  
  factory CounterValue(int value) {
    if (value < 0) throw ArgumentError('Cannot be negative');
    return CounterValue._(value);
  }
  
  CounterValue increment() => CounterValue._(number + 1);
  CounterValue decrement() => number == 0 ? this : CounterValue._(number - 1);
}

// Entity (Domain)
class CounterEntity extends Equatable {
  final String id;
  final CounterValue value;
  
  CounterEntity increment() {
    return CounterEntity(id: id, value: value.increment());
  }
}

// Repository Interface (Domain)
abstract class CounterRepository {
  Future<Either<Failure, CounterEntity>> getCounter();
}

// Use Case (Application)
class IncrementCounterUseCase {
  Future<Either<Failure, CounterEntity>> execute() async {
    final current = await repository.getCounter();
    return current.fold(
      (failure) => Left(failure),
      (counter) async {
        final updated = counter.increment();
        await repository.saveCounter(updated);
        return Right(updated);
      },
    );
  }
}

// Repository Implementation (Infrastructure)
class CounterRepositoryImpl implements CounterRepository {
  // DTO, DataSource, etc.
}

📝 Notes Feature Comparison

Data Models

MVC/MVVM

class NoteModel {
  String id;
  String content;
  DateTime createdAt;
}

Clean Architecture

// Entity (Domain)
class Note {
  final String id;
  final String content;
  final DateTime createdAt;
  
  Note({required this.id, required this.content, required this.createdAt});
}

// Model (Data)
class NoteModel {
  // JSON serialization
  Map<String, dynamic> toJson() { ... }
  factory NoteModel.fromJson(Map<String, dynamic> json) { ... }
  
  // Convert to entity
  Note toEntity() { ... }
}

DDD

// Value Objects (Domain)
class NoteId extends Equatable {
  final String value;
}

class NoteContent extends Equatable {
  final String text;
  
  factory NoteContent(String content) {
    if (content.isEmpty) throw ArgumentError('Cannot be empty');
    if (content.length > 500) throw ArgumentError('Too long');
    return NoteContent._(content.trim());
  }
}

class NoteTimestamp extends Equatable {
  final DateTime dateTime;
  
  String get formatted => ...;
  String get relative => ...;
}

// Entity (Domain)
class NoteEntity extends Equatable {
  final NoteId id;
  final NoteContent content;
  final NoteTimestamp createdAt;
}

// DTO (Infrastructure)
class NoteDto {
  // JSON serialization and entity conversion
}

🔄 State Management Comparison

All Patterns Use BLoC/Cubit

MVC

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterInitial());
  
  final model = CounterModel();
  
  void increment() {
    model.value++;
    emit(CounterUpdated(model.value));
  }
}

// In View
BlocBuilder<CounterCubit, CounterState>(
  builder: (context, state) {
    if (state is CounterUpdated) {
      return Text('${state.value}');
    }
    return Text('0');
  },
)

MVVM

class CounterViewModel extends Cubit<int> {
  CounterViewModel() : super(0);
  
  void increment() => emit(state + 1);
}

// In View
BlocBuilder<CounterViewModel, int>(
  builder: (context, count) => Text('$count'),
)

Clean Architecture

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  final IncrementCounterUseCase incrementCounterUseCase;
  
  CounterBloc({required this.incrementCounterUseCase})
      : super(CounterInitial()) {
    on<IncrementCounter>(_onIncrement);
  }
  
  Future<void> _onIncrement(
    IncrementCounter event,
    Emitter<CounterState> emit,
  ) async {
    final result = await incrementCounterUseCase.execute();
    emit(CounterUpdated(result.value));
  }
}

// In View
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    if (state is CounterUpdated) {
      return Text('${state.value}');
    }
    return Text('0');
  },
)

DDD

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  final IncrementCounterUseCase incrementCounterUseCase;
  
  CounterBloc({required this.incrementCounterUseCase})
      : super(CounterInitial()) {
    on<IncrementCounter>(_onIncrement);
  }
  
  Future<void> _onIncrement(
    IncrementCounter event,
    Emitter<CounterState> emit,
  ) async {
    final result = await incrementCounterUseCase.execute();
    result.fold(
      (failure) => emit(CounterError(failure.message)),
      (counter) => emit(CounterUpdated(counter.value.number)),
    );
  }
}

// In View
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    if (state is CounterUpdated) {
      return Text('${state.value}');
    }
    return Text('0');
  },
)

🧪 Testability Comparison

MVC - Moderate Testability

test('Counter increment', () {
  final controller = CounterController();
  controller.increment();
  expect(controller.model.value.value, 1);
});

Issues:

  • Controller tightly coupled to Model
  • Hard to mock dependencies
  • UI logic mixed with business logic

MVVM - Good Testability

test('Counter increment', () {
  final viewModel = CounterViewModel();
  viewModel.increment();
  expect(viewModel.counterValue, 1);
});

Improvements:

  • ViewModel independent of View
  • Can test presentation logic in isolation
  • Easier to mock

Clean Architecture - Excellent Testability

test('GetCounterUseCase returns counter', () async {
  final mockRepo = MockCounterRepository();
  when(() => mockRepo.getCounter()).thenAnswer((_) async => Counter(0));
  
  final useCase = GetCounterUseCase(mockRepo);
  final result = await useCase.execute();
  
  expect(result.value, 0);
});

Improvements:

  • Each layer tested independently
  • Easy to mock with repository pattern
  • Use cases contain testable business logic

DDD - Best Testability

// Test Value Object
test('CounterValue cannot be negative', () {
  expect(() => CounterValue(-1), throwsArgumentError);
});

// Test Entity
test('Counter increment increases value', () {
  final counter = CounterEntity(id: '1', value: CounterValue(0));
  final updated = counter.increment();
  expect(updated.value.number, 1);
});

// Test Use Case
test('IncrementCounterUseCase increments counter', () async {
  final mockRepo = MockCounterRepository();
  final useCase = IncrementCounterUseCase(mockRepo);
  
  final result = await useCase.execute();
  
  expect(result.isRight(), true);
});

Improvements:

  • Pure domain logic (no dependencies)
  • Value objects enforce business rules
  • Each layer fully isolated
  • Comprehensive test coverage possible

📊 Performance Comparison

Aspect MVC MVVM Clean DDD
App Startup ⚡ Fastest ⚡ Fast 🔄 Moderate 🔄 Slowest
Memory Usage ✅ Lowest ✅ Low 🔄 Moderate ⚠️ Higher
Build Time ⚡ Fastest ⚡ Fast 🔄 Moderate 🔄 Slowest
File Loading ⚡ Minimal ✅ Few 🔄 Many ⚠️ Most
Hot Reload ⚡ Instant ⚡ Instant ✅ Fast ✅ Fast

Note: Performance differences are negligible for small-medium apps. Only noticeable in large-scale applications.


🎓 Learning Path Recommendation

Beginner → Advanced

  1. Start with MVC (Week 1)

    • Learn basic Flutter concepts
    • Understand separation of concerns
    • Build simple apps quickly
  2. Move to MVVM (Week 2-3)

    • Learn reactive programming
    • Understand two-way binding
    • Build UI-heavy apps
  3. Learn Clean Architecture (Week 4-6)

    • Understand layer separation
    • Learn use case pattern
    • Build scalable apps
  4. Master DDD (Week 7-12)

    • Learn tactical patterns
    • Understand value objects
    • Build enterprise apps

🏆 When to Use Each Pattern

Use MVC When:

  • Building a simple app (< 5 screens)
  • Rapid prototyping
  • Learning Flutter basics
  • Personal projects
  • MVP development

Use MVVM When:

  • Medium-sized app (5-15 screens)
  • Complex UI state management
  • Two-way data binding needed
  • Team with MVVM experience
  • Reactive programming preferred

Use Clean Architecture When:

  • Large app (15+ screens)
  • Multiple developers
  • High testability required
  • Long-term maintenance
  • Need to swap implementations easily

Use DDD When:

  • Enterprise applications
  • Complex business logic
  • Multiple bounded contexts
  • Evolving requirements
  • Domain experts involved
  • Microservices architecture
  • Large development team

📱 Real-World Examples

MVC Suitable For:

  • Todo list apps
  • Calculator apps
  • Timer apps
  • Simple CRUD apps
  • Personal finance tracker

MVVM Suitable For:

  • Social media apps
  • Chat applications
  • E-commerce apps
  • Dashboard apps
  • Form-heavy apps

Clean Architecture Suitable For:

  • Banking applications
  • Health tracking apps
  • Multi-platform apps
  • API-heavy applications
  • Apps with offline support

DDD Suitable For:

  • ERP systems
  • Healthcare management
  • Financial trading platforms
  • E-commerce platforms
  • Booking systems
  • Inventory management
  • Complex workflow apps

✅ Decision Matrix

Answer these questions to choose:

  1. App Complexity?

    • Simple → MVC
    • Moderate → MVVM
    • Complex → Clean/DDD
  2. Team Size?

    • Solo → MVC/MVVM
    • Small (2-5) → MVVM/Clean
    • Large (5+) → Clean/DDD
  3. Business Logic Complexity?

    • Simple → MVC/MVVM
    • Moderate → Clean
    • Complex → DDD
  4. Testability Priority?

    • Low → MVC
    • Moderate → MVVM
    • High → Clean
    • Highest → DDD
  5. Development Timeline?

    • Short (< 1 month) → MVC
    • Medium (1-3 months) → MVVM
    • Long (3-6 months) → Clean
    • Very Long (6+ months) → DDD
  6. Future Maintenance?

    • Short-term → MVC
    • Medium-term → MVVM
    • Long-term → Clean/DDD

📚 Summary

All four patterns are now standardized with identical features and UI, making it easy to:

✅ Compare architectures side-by-side
✅ Learn progression from simple to complex
✅ Choose the right pattern for your project
✅ Understand trade-offs and benefits
✅ See code structure differences clearly

Choose based on your project needs, not personal preference! 🎯