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
+288
View File
@@ -0,0 +1,288 @@
# ViewModel Communication Patterns
## ❌ The Problem: Direct ViewModel Dependencies
```dart
class NotificationViewModel {
final HomeViewModel _homeViewModel; // ❌ BAD: Direct dependency
void markAsRead() {
_homeViewModel.setUnreadNotificationFalse(); // ❌ Tight coupling
}
}
```
**Why is this bad?**
- Tight coupling between ViewModels
- Difficult to test in isolation
- Circular dependency risk
- Violates Single Responsibility Principle
- Hard to maintain and scale
---
## ✅ Solution 1: Shared Service Pattern (BEST for your case)
Create a dedicated service that manages shared state.
### Implementation:
**Service:**
```dart
// lib/data/services/notification_state_service.dart
class NotificationStateService extends ChangeNotifier {
bool _hasUnreadNotification = false;
bool get hasUnreadNotification => _hasUnreadNotification;
void setHasUnreadNotification(bool value) {
if (_hasUnreadNotification != value) {
_hasUnreadNotification = value;
notifyListeners();
}
}
}
```
**ViewModels depend on the service:**
```dart
class HomeViewModel {
final NotificationStateService _notificationStateService;
void checkUnreadNotifications() async {
// ... fetch from API
_notificationStateService.setHasUnreadNotification(hasUnread);
}
}
class NotificationViewModel {
final NotificationStateService _notificationStateService;
void markAsRead() {
// ... mark as read
_notificationStateService.setHasUnreadNotification(false);
}
}
```
**UI consumes the service directly:**
```dart
class NotificationBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasUnread = context.watch<NotificationStateService>()
.hasUnreadNotification;
return Badge(showBadge: hasUnread);
}
}
```
**Benefits:**
- ✅ No ViewModel-to-ViewModel dependency
- ✅ Single source of truth
- ✅ Easy to test
- ✅ Follows SOLID principles
- ✅ Scalable
---
## ✅ Solution 2: Repository Pattern
Move the shared logic to a repository that both ViewModels depend on.
```dart
// Repository manages the domain logic
class NotificationRepository {
bool _hasUnread = false;
Future<bool> checkUnreadNotifications() async {
final result = await api.checkUnread();
_hasUnread = result.hasUnread;
return _hasUnread;
}
Future<void> markAllAsRead() async {
await api.markAllAsRead();
_hasUnread = false;
}
}
// ViewModels only depend on repository
class HomeViewModel {
final NotificationRepository _repo;
void init() async {
final hasUnread = await _repo.checkUnreadNotifications();
notifyListeners();
}
}
```
---
## ✅ Solution 3: Event Bus / Stream Pattern
ViewModels communicate through events without knowing about each other.
```dart
// Event bus service
class AppEventBus {
final _controller = StreamController<AppEvent>.broadcast();
Stream<AppEvent> get events => _controller.stream;
void emit(AppEvent event) => _controller.add(event);
}
// Events
abstract class AppEvent {}
class NotificationReadEvent extends AppEvent {}
// ViewModel 1: Emits events
class NotificationViewModel {
final AppEventBus _eventBus;
void markAsRead() {
// ... mark as read
_eventBus.emit(NotificationReadEvent());
}
}
// ViewModel 2: Listens to events
class HomeViewModel {
final AppEventBus _eventBus;
HomeViewModel() {
_eventBus.events.listen((event) {
if (event is NotificationReadEvent) {
_handleNotificationRead();
}
});
}
}
```
---
## ✅ Solution 4: UI Coordination
Let the UI layer coordinate between ViewModels.
```dart
class NotificationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final notificationVM = context.watch<NotificationViewModel>();
final homeVM = context.watch<HomeViewModel>();
return Button(
onPressed: () {
// UI coordinates the interaction
notificationVM.markAsRead();
homeVM.refreshNotificationBadge();
},
);
}
}
```
**When to use:**
- Simple interactions
- UI-specific coordination
- Few ViewModels involved
---
## ✅ Solution 5: Riverpod Pattern (Advanced)
Use Riverpod's provider dependencies for better state management.
```dart
// Shared state provider
final notificationStateProvider = StateNotifierProvider<NotificationState, bool>(
(ref) => NotificationState(),
);
class NotificationState extends StateNotifier<bool> {
NotificationState() : super(false);
void setHasUnread(bool value) => state = value;
}
// ViewModels depend on the provider
final homeViewModelProvider = Provider((ref) {
final notificationState = ref.read(notificationStateProvider.notifier);
return HomeViewModel(notificationState);
});
```
---
## 📊 Comparison Table
| Pattern | Complexity | Testability | Scalability | Best For |
|---------|-----------|-------------|-------------|----------|
| Shared Service | Low | High | High | Shared state across app |
| Repository | Medium | High | High | Domain logic sharing |
| Event Bus | Medium | Medium | High | Loosely coupled events |
| UI Coordination | Low | Medium | Low | Simple interactions |
| Riverpod | Medium | High | Very High | New projects |
---
## 🎯 Recommendation for Your Project
Use **Solution 1: Shared Service Pattern** because:
1. ✅ Your codebase already uses Provider
2. ✅ The notification state is truly shared across the app
3. ✅ Simple to implement and understand
4. ✅ Easy to test
5. ✅ Follows your modular architecture principle
6. ✅ No major refactoring required
---
## 📝 Implementation Checklist
- [x] Create `NotificationStateService`
- [x] Add service to dependency injection
- [x] Update `HomeViewModel` to use service
- [x] Update `NotificationViewModel` to use service
- [x] Remove direct ViewModel dependencies
- [x] Test the implementation
- [ ] Remove the example files when done
---
## 🧪 Testing Example
```dart
test('NotificationViewModel updates shared state', () {
final stateService = NotificationStateService();
final repository = MockNotificationRepository();
final viewModel = NotificationViewModel(
notificationsRepository: repository,
notificationStateService: stateService,
);
// Act
viewModel.markAllAsRead();
// Assert
expect(stateService.hasUnreadNotification, false);
});
```
---
## 🚀 Key Takeaway
**ViewModels should NEVER depend on other ViewModels.**
Instead:
- Use a **shared service** for shared state
- Use **repositories** for domain logic
- Use **event buses** for loose coupling
- Let the **UI coordinate** simple interactions