# 🔧 DI & State Management Refactoring Plan ## 🚨 Critical Issues Found ### 1. ViewModel-to-ViewModel Dependencies **Current:** ViewModels directly depend on other ViewModels **Problem:** Tight coupling, difficult testing, cascading rebuilds **Solution:** Use shared repositories or event bus ### 2. Eager Instantiation **Current:** All 25 providers created at app startup **Problem:** Slow startup, memory waste **Solution:** Lazy loading with ProxyProvider ### 3. Error Handling Anti-Pattern **Current:** `_errorMessage = error; notifyListeners(); _errorMessage = null; notifyListeners();` **Problem:** Errors flash and disappear instantly **Solution:** Proper error state with timed clearing ### 4. Large ViewModels (338-525 lines) **Current:** Monolithic ViewModels with mixed responsibilities **Problem:** Violates SRP, hard to maintain **Solution:** Split into smaller, focused controllers --- ## 📋 Refactoring Strategy ### Phase 1: Remove ViewModel Dependencies (Week 1) #### Option A: Shared Repository Pattern (Recommended) ```dart // ✅ BEFORE: TendersViewModel depends on HomeViewModel class TendersViewModel { final HomeViewModel homeViewModel; // ❌ BAD void updateFeedback() { homeViewModel.init(); // Coupled! } } // ✅ AFTER: Both share TendersRepository class TendersViewModel { final TendersRepository _repository; // ✅ GOOD Future updateFeedback() async { await _repository.refreshTenders(); // Repository notifies all listeners } } class HomeViewModel { final TendersRepository _repository; // ✅ Same repository void init() { _repository.addListener(_onTendersChanged); } } ``` #### Option B: Event Bus Pattern ```dart // Create event bus class AppEventBus { final _controller = StreamController.broadcast(); Stream get events => _controller.stream; void fire(AppEvent event) => _controller.add(event); } // Events abstract class AppEvent {} class TenderUpdatedEvent extends AppEvent { final String tenderId; } // ViewModels listen to events class HomeViewModel { void init() { eventBus.events .whereType() .listen(_onTenderUpdated); } } ``` --- ### Phase 2: Implement Lazy Loading (Week 1) ```dart // ✅ AFTER: Lazy loading with ProxyProvider List get viewModels { return [ // Only AuthViewModel and ThemeProvider are eager ChangeNotifierProvider( create: (context) => AuthViewModel( authRepository: context.read(), ), ), // Others are lazy - created on first access ChangeNotifierProxyProvider( create: (_) => HomeViewModel(homeRepository: _), update: (_, auth, previous) => previous ?? HomeViewModel(...), lazy: true, // ✅ Not created until needed ), ]; } // Remove init() from constructor class HomeViewModel { HomeViewModel({required HomeRepository homeRepository}) : _homeRepository = homeRepository; // ✅ Don't call init() here } ``` --- ### Phase 3: Fix Error Handling (Week 1) ```dart // ✅ Create proper error state class ErrorState { final String message; final DateTime timestamp; final Duration displayDuration; bool get shouldDisplay => DateTime.now().difference(timestamp) < displayDuration; } // In ViewModel ErrorState? _errorState; Timer? _errorTimer; void _setError(String message) { _errorState = ErrorState( message: message, timestamp: DateTime.now(), displayDuration: Duration(seconds: 3), ); _errorTimer?.cancel(); _errorTimer = Timer(Duration(seconds: 3), () { _errorState = null; notifyListeners(); }); notifyListeners(); } ``` --- ### Phase 4: Introduce State Classes (Week 2) ```dart // ✅ Create immutable state classes with Freezed @freezed class TendersState with _$TendersState { const factory TendersState({ @Default(LoadingStatus.idle) LoadingStatus status, @Default([]) List tenders, @Default([]) List feedbacks, @Default(PaginationState()) PaginationState pagination, ErrorState? error, }) = _TendersState; } enum LoadingStatus { idle, loading, loadingMore, success, failure } @freezed class PaginationState with _$PaginationState { const factory PaginationState({ @Default(1) int currentPage, @Default(true) bool hasMore, @Default(10) int pageSize, }) = _PaginationState; } // ViewModel becomes much simpler class TendersViewModel extends StateNotifier { TendersViewModel() : super(TendersState()); Future loadTenders() async { state = state.copyWith(status: LoadingStatus.loading); final result = await _repository.getTenders(); state = result.when( ok: (data) => state.copyWith( status: LoadingStatus.success, tenders: data.tenders, ), error: (error) => state.copyWith( status: LoadingStatus.failure, error: ErrorState(message: error.toString()), ), ); } } ``` --- ### Phase 5: Split Large ViewModels (Week 2-3) #### Before: 525 lines in TendersViewModel ```dart class TendersViewModel { // Tenders logic (150 lines) // Feedback logic (150 lines) // Filtering logic (100 lines) // Sorting logic (75 lines) // Pagination logic (50 lines) } ``` #### After: Multiple focused files ``` view_models/tenders/ ├── tenders_view_model.dart (50 lines - orchestrator) ├── controllers/ │ ├── tenders_loader.dart (60 lines) │ ├── feedback_controller.dart (70 lines) │ └── pagination_controller.dart (50 lines) ├── filters/ │ ├── tenders_filter_state.dart (30 lines) │ └── tenders_sort_state.dart (30 lines) └── state/ └── tenders_state.dart (40 lines) ``` --- ## 🎯 Better State Management Options ### Option 1: Riverpod (Recommended) **Pros:** - ✅ Compile-time safety - ✅ No BuildContext needed - ✅ Built-in testing support - ✅ Auto-dispose - ✅ Family/AutoDispose modifiers - ✅ No ViewModel dependencies **Cons:** - 🟡 Learning curve - 🟡 Migration effort ### Option 2: Bloc/Cubit **Pros:** - ✅ Clear state transitions - ✅ Built-in testing - ✅ Event-driven - ✅ Time-travel debugging **Cons:** - 🟡 More boilerplate - 🟡 Steeper learning curve ### Option 3: Stick with Provider + Refactor **Pros:** - ✅ No migration needed - ✅ Team knows it **Cons:** - 🔴 Still requires manual state management - 🔴 No compile-time safety - 🔴 Prone to same issues --- ## 📊 Migration Effort Estimate | Phase | Effort | Priority | Files Affected | |-------|--------|----------|----------------| | 1. Remove VM deps | 3 days | 🔴 Critical | 9 ViewModels, dependencies.dart | | 2. Lazy loading | 1 day | 🟡 High | dependencies.dart, main.dart | | 3. Fix error handling | 2 days | 🟡 High | 9 ViewModels | | 4. State classes | 5 days | 🟢 Medium | 9 ViewModels + new state files | | 5. Split ViewModels | 7 days | 🟢 Medium | 9 ViewModels → 30+ files | **Total: ~18 days (3.5 weeks) for complete refactor** --- ## 🔍 Quick Wins (Can Do Today) ### 1. Fix Error Handling (2 hours) ```dart // Create lib/core/state/error_state.dart // Update all ViewModels to use it ``` ### 2. Remove init() from Constructors (1 hour) ```dart // Move all init() calls to explicit method calls ``` ### 3. Add State Classes (1 day) ```dart // Start with AuthState, HomeState ``` --- ## 📚 Resources - [Riverpod Migration Guide](https://riverpod.dev/docs/from_provider) - [Flutter State Management Comparison](https://docs.flutter.dev/data-and-backend/state-mgmt/options) - [Provider Best Practices](https://pub.dev/packages/provider#best-practices)