Files
tm_app/REFACTORING_PLAN.md

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)