add favorite

This commit is contained in:
Phuoc Nguyen
2025-11-18 11:23:07 +07:00
parent 192c322816
commit a5eb95fa64
25 changed files with 2506 additions and 978 deletions

View File

@@ -0,0 +1,198 @@
# Favorites Page - Loading State Fix
## Problem
Users were seeing the empty state ("Chưa có sản phẩm yêu thích") flash briefly before the actual favorites data loaded, even when they had favorites. This created a poor user experience.
## Root Cause
The `favoriteProductsProvider` is an async provider that:
1. Loads favorites from API/cache
2. Fetches all products
3. Filters to get favorite products
During this process, the provider goes through these states:
- **Loading** → Returns empty list [] → Shows loading skeleton
- **Data** → If products.isEmpty → Shows empty state ❌ **FLASH**
- **Data** → Returns actual products → Shows grid
The flash happened because when the provider rebuilt, it momentarily returned an empty list before the actual data arrived.
## Solution
### 1. Added `ref.keepAlive()` to Providers
```dart
@riverpod
class Favorites extends _$Favorites {
@override
Future<Set<String>> build() async {
ref.keepAlive(); // ← Prevents provider disposal
// ... rest of code
}
}
@riverpod
Future<List<Product>> favoriteProducts(Ref ref) async {
ref.keepAlive(); // ← Keeps previous data in memory
// ... rest of code
}
```
**Benefits**:
- Prevents state from being disposed when widget rebuilds
- Keeps previous data available via `favoriteProductsAsync.valueOrNull`
- Reduces unnecessary API calls
### 2. Smart Loading State Logic
```dart
loading: () {
// 1. Check for previous data first
final previousValue = favoriteProductsAsync.valueOrNull;
// 2. If we have previous data, show it while loading
if (previousValue != null && previousValue.isNotEmpty) {
return Stack([
_FavoritesGrid(products: previousValue), // Show old data
LoadingIndicator(), // Small loading badge on top
]);
}
// 3. Use favoriteCount as a hint
if (favoriteCount > 0) {
return LoadingState(); // Show skeleton
}
// 4. No data, show skeleton (not empty state)
return LoadingState();
}
```
### 3. Data State - Only Show Empty When Actually Empty
```dart
data: (products) {
// Only show empty state when data is actually empty
if (products.isEmpty) {
return const _EmptyState();
}
return RefreshIndicator(
child: _FavoritesGrid(products: products),
);
}
```
## User Experience Flow
### Before Fix ❌
```
User opens favorites page
Loading skeleton shows (0.1s)
Empty state flashes (0.2s) ⚡ BAD UX
Favorites grid appears
```
### After Fix ✅
```
User opens favorites page
Loading skeleton shows (0.3s)
Favorites grid appears smoothly
```
**OR** if returning to page:
```
User opens favorites page (2nd time)
Previous favorites show immediately
Small "Đang tải..." badge appears at top
Updated favorites appear (if changed)
```
## Additional Benefits
### 1. Better Offline Support
- When offline, previous data stays visible
- Shows error banner on top instead of hiding content
- User can still browse cached favorites
### 2. Faster Perceived Performance
- Instant display of previous data
- Users don't see empty states during reloads
- Smoother transitions
### 3. Error Handling
```dart
error: (error, stackTrace) {
final previousValue = favoriteProductsAsync.valueOrNull;
// Show previous data with error message
if (previousValue != null && previousValue.isNotEmpty) {
return Stack([
_FavoritesGrid(products: previousValue),
ErrorBanner(onRetry: ...),
]);
}
// No previous data, show full error state
return _ErrorState();
}
```
## Files Modified
1. **lib/features/favorites/presentation/providers/favorites_provider.dart**
- Added `ref.keepAlive()` to `Favorites` class (line 81)
- Added `ref.keepAlive()` to `favoriteProducts` provider (line 271)
2. **lib/features/favorites/presentation/pages/favorites_page.dart**
- Enhanced loading state logic (lines 138-193)
- Added previous value checking
- Added favoriteCount hint logic
## Testing Checklist
- [x] No empty state flash on first load
- [x] Smooth loading with skeleton
- [x] Previous data shown on subsequent visits
- [x] Loading indicator overlay when refreshing
- [ ] Test with slow network (3G)
- [ ] Test with offline mode
- [ ] Test with errors during load
## Performance Impact
**Positive**:
- Reduced state rebuilds
- Better memory management with keepAlive
- Fewer API calls on navigation
⚠️ **Watch**:
- Memory usage (keepAlive keeps data in memory)
- Can manually dispose with `ref.invalidate()` if needed
## Future Improvements
1. **Add shimmer duration control**
- Minimum shimmer display time to prevent flash
- Smooth fade transition from skeleton to content
2. **Progressive loading**
- Show cached data first
- Overlay with "Updating..." badge
- Fade in updated items
3. **Prefetch on app launch**
- Load favorites in background
- Data ready before user navigates to page
---
**Status**: ✅ Implemented
**Impact**: High - Significantly improves perceived performance
**Breaking Changes**: None