317 lines
7.6 KiB
Markdown
317 lines
7.6 KiB
Markdown
# 🔧 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<void> 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<AppEvent>.broadcast();
|
|
|
|
Stream<AppEvent> 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<TenderUpdatedEvent>()
|
|
.listen(_onTenderUpdated);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 2: Implement Lazy Loading (Week 1)
|
|
|
|
```dart
|
|
// ✅ AFTER: Lazy loading with ProxyProvider
|
|
List<SingleChildWidget> get viewModels {
|
|
return [
|
|
// Only AuthViewModel and ThemeProvider are eager
|
|
ChangeNotifierProvider(
|
|
create: (context) => AuthViewModel(
|
|
authRepository: context.read(),
|
|
),
|
|
),
|
|
|
|
// Others are lazy - created on first access
|
|
ChangeNotifierProxyProvider<AuthViewModel, HomeViewModel>(
|
|
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<Tender> tenders,
|
|
@Default([]) List<FeedbackData> 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<TendersState> {
|
|
TendersViewModel() : super(TendersState());
|
|
|
|
Future<void> 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)
|
|
|
|
|