update md
This commit is contained in:
198
docs/md/favorites_loading_fix.md
Normal file
198
docs/md/favorites_loading_fix.md
Normal 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
|
||||
Reference in New Issue
Block a user