Update .gitignore to exclude Firebase configuration files and refactor notification handling in the app. Removed redundant logging and notification data storage in SharedPreferences. Introduced NotificationCheckService and NotificationStateService for improved notification management. Updated view models and UI components to utilize the new services, enhancing notification state tracking and user experience.
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
# 🔧 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user