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:
amirrezaghabeli
2025-10-06 11:59:54 +03:30
parent 17634744f7
commit 42f2e93a8e
28 changed files with 1499 additions and 595 deletions
+316
View File
@@ -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)