# 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> build() async { ref.keepAlive(); // ← Prevents provider disposal // ... rest of code } } @riverpod Future> 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