Favorites Feature
A complete favorites state management system for the Worker app using Riverpod 3.0 with code generation and Hive for local persistence.
📁 File Structure
lib/features/favorites/
├── data/
│ ├── datasources/
│ │ └── favorites_local_datasource.dart # Hive operations
│ └── models/
│ ├── favorite_model.dart # Hive model (TypeId: 28)
│ └── favorite_model.g.dart # Generated adapter
├── domain/
│ └── entities/
│ └── favorite.dart # Business entity
├── presentation/
│ └── providers/
│ ├── favorites_provider.dart # Riverpod providers
│ └── favorites_provider.g.dart # Generated providers
├── INTEGRATION_SUMMARY.md # Complete documentation
├── USAGE_EXAMPLES.md # Code examples
└── README.md # This file
🚀 Quick Start
1. Import Provider
import 'package:worker/features/favorites/presentation/providers/favorites_provider.dart';
2. Check if Product is Favorited
final isFavorited = ref.watch(isFavoriteProvider(productId));
3. Toggle Favorite
ref.read(favoritesProvider.notifier).toggleFavorite(productId);
4. Get Favorites Count
final count = ref.watch(favoriteCountProvider);
📦 Available Providers
| Provider | Type | Description |
|---|---|---|
favoritesProvider |
AsyncNotifier<Set<String>> |
Main favorites state |
isFavoriteProvider(productId) |
Provider<bool> |
Check if product is favorited |
favoriteCountProvider |
Provider<int> |
Total count of favorites |
favoriteProductIdsProvider |
Provider<List<String>> |
All favorite product IDs |
favoritesLocalDataSourceProvider |
Provider<DataSource> |
Database access |
currentUserIdProvider |
Provider<String> |
Current user ID (TODO: auth) |
🎯 Common Use Cases
Favorite Button in Product Card
class ProductCard extends ConsumerWidget {
final String productId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final isFavorited = ref.watch(isFavoriteProvider(productId));
return IconButton(
icon: Icon(
isFavorited ? Icons.favorite : Icons.favorite_border,
color: isFavorited ? Colors.red : Colors.grey,
),
onPressed: () {
ref.read(favoritesProvider.notifier).toggleFavorite(productId);
},
);
}
}
Favorites Count Badge
class FavoriteBadge extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(favoriteCountProvider);
return Badge(
label: Text('$count'),
child: Icon(Icons.favorite),
);
}
}
Display All Favorites
class FavoritesPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final favoritesAsync = ref.watch(favoritesProvider);
return favoritesAsync.when(
data: (favorites) => ListView.builder(
itemCount: favorites.length,
itemBuilder: (context, index) {
final productId = favorites.elementAt(index);
return ProductTile(productId: productId);
},
),
loading: () => const CustomLoadingIndicator(),
error: (error, stack) => ErrorWidget(error),
);
}
}
🔧 Provider Methods
Main Provider (favoritesProvider.notifier)
| Method | Description |
|---|---|
addFavorite(productId) |
Add product to favorites |
removeFavorite(productId) |
Remove product from favorites |
toggleFavorite(productId) |
Toggle favorite status |
refresh() |
Reload favorites from database |
clearAll() |
Remove all favorites |
💾 Data Persistence
- Storage: Hive local database
- Box:
favorite_box(HiveBoxNames.favoriteBox) - Model:
FavoriteModel(TypeId: 28) - Fields:
favoriteId: Unique ID (userId_productId_timestamp)productId: Product referenceuserId: User referencecreatedAt: Timestamp
⚠️ Important Notes
TODO: Auth Integration
Currently using hardcoded userId ('user_001'). Replace when auth is ready:
// In favorites_provider.dart
@riverpod
String currentUserId(Ref ref) {
// TODO: Replace with actual auth provider
final user = ref.watch(authProvider).user;
return user?.id ?? 'guest';
}
Hive Box Initialization
Ensure the favorites box is opened in main.dart:
await Hive.openBox<FavoriteModel>(HiveBoxNames.favoriteBox);
📚 Documentation
- INTEGRATION_SUMMARY.md - Complete technical documentation
- USAGE_EXAMPLES.md - Comprehensive code examples
✅ Features
- ✅ Add/remove favorites
- ✅ Toggle favorite status
- ✅ Persistent storage (Hive)
- ✅ Multi-user support
- ✅ Optimistic updates
- ✅ Error handling
- ✅ Loading states
- ✅ Debug logging
- ✅ Auto-dispose providers
🧪 Testing
test('should add favorite', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
await container.read(favoritesProvider.notifier)
.addFavorite('product_1');
final favorites = await container.read(favoritesProvider.future);
expect(favorites, contains('product_1'));
});
🎨 UI Integration Examples
See USAGE_EXAMPLES.md for:
- Favorite buttons with loading states
- Favorites page with pull-to-refresh
- Empty state handling
- Error state handling
- App lifecycle management
- Performance optimization
🐛 Debugging
All operations log to console:
[FavoritesProvider] Added favorite: product_123
[FavoritesLocalDataSource] Loaded 5 favorites for user: user_001
[FavoritesProvider] Error adding favorite: ...
📈 Performance
- O(1) favorite lookup using
Set<String> - Auto-dispose providers when not in use
- Selective rebuilds with
isFavoriteProvider - Database compaction available
🔮 Future Enhancements
- Remote API sync
- Offline queue
- Cross-device sync
- Batch operations
- Favorites collections
- Share favorites
- Analytics tracking
🆘 Troubleshooting
| Issue | Solution |
|---|---|
| Favorites not persisting | Check Hive box initialization in main.dart |
| State not updating | Use ref.read() for mutations, ref.watch() for listening |
| Performance issues | Use isFavoriteProvider instead of watching full favoritesProvider |
📞 Support
For detailed examples and advanced use cases, see:
Status: ✅ Ready for integration Version: 1.0.0 Last Updated: 2024-10-24