239 lines
6.8 KiB
Markdown
239 lines
6.8 KiB
Markdown
# Cart Initialization & Keep Alive Implementation
|
|
|
|
## Overview
|
|
The cart is now initialized when the app starts (on HomePage mount) and kept alive throughout the entire app session. This ensures:
|
|
- Cart data is loaded from API once on startup
|
|
- Cart state persists across all navigation
|
|
- No unnecessary re-fetching when navigating between pages
|
|
- Real-time cart badge updates across all screens
|
|
|
|
## Implementation Details
|
|
|
|
### 1. Cart Provider with Keep Alive
|
|
**File**: `lib/features/cart/presentation/providers/cart_provider.dart`
|
|
|
|
```dart
|
|
@Riverpod(keepAlive: true) // ✅ Keep alive throughout app session
|
|
class Cart extends _$Cart {
|
|
@override
|
|
CartState build() {
|
|
return CartState.initial().copyWith(
|
|
memberTier: 'Diamond',
|
|
memberDiscountPercent: 15.0,
|
|
);
|
|
}
|
|
|
|
Future<void> initialize() async {
|
|
// Load cart from API with Hive fallback
|
|
// ...
|
|
}
|
|
}
|
|
|
|
// Dependent providers also need keepAlive
|
|
@Riverpod(keepAlive: true)
|
|
int cartItemCount(Ref ref) {
|
|
final cartState = ref.watch(cartProvider);
|
|
return cartState.items.length;
|
|
}
|
|
|
|
@Riverpod(keepAlive: true)
|
|
double cartTotal(Ref ref) {
|
|
final cartState = ref.watch(cartProvider);
|
|
return cartState.total;
|
|
}
|
|
```
|
|
|
|
### 1.1 Cart Data Providers with Keep Alive
|
|
**File**: `lib/features/cart/data/providers/cart_data_providers.dart`
|
|
|
|
**CRITICAL**: All cart data layer providers must also use `keepAlive: true` to prevent disposal errors:
|
|
|
|
```dart
|
|
@Riverpod(keepAlive: true)
|
|
CartLocalDataSource cartLocalDataSource(Ref ref) {
|
|
final hiveService = HiveService();
|
|
return CartLocalDataSourceImpl(hiveService);
|
|
}
|
|
|
|
@Riverpod(keepAlive: true)
|
|
Future<CartRemoteDataSource> cartRemoteDataSource(Ref ref) async {
|
|
final dioClient = await ref.watch(dioClientProvider.future);
|
|
return CartRemoteDataSourceImpl(dioClient);
|
|
}
|
|
|
|
@Riverpod(keepAlive: true)
|
|
Future<CartRepository> cartRepository(Ref ref) async {
|
|
final remoteDataSource = await ref.watch(cartRemoteDataSourceProvider.future);
|
|
final localDataSource = ref.watch(cartLocalDataSourceProvider);
|
|
return CartRepositoryImpl(
|
|
remoteDataSource: remoteDataSource,
|
|
localDataSource: localDataSource,
|
|
);
|
|
}
|
|
```
|
|
|
|
**Why all providers need keepAlive:**
|
|
- Cart provider depends on cartRepository
|
|
- If repository is disposed, cart operations fail with "Ref disposed" error
|
|
- All dependencies in the chain must persist together
|
|
- Ensures consistent lifecycle management
|
|
|
|
**Benefits of `keepAlive: true`:**
|
|
- Provider state is never disposed
|
|
- Cart data persists when navigating away and back
|
|
- No re-initialization needed on subsequent visits
|
|
- Consistent cart count across all app screens
|
|
- No "Ref disposed" errors during async operations
|
|
|
|
### 2. HomePage Initialization
|
|
**File**: `lib/features/home/presentation/pages/home_page.dart`
|
|
|
|
```dart
|
|
class HomePage extends ConsumerStatefulWidget {
|
|
const HomePage({super.key});
|
|
|
|
@override
|
|
ConsumerState<HomePage> createState() => _HomePageState();
|
|
}
|
|
|
|
class _HomePageState extends ConsumerState<HomePage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Initialize cart from API on app startup
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
ref.read(cartProvider.notifier).initialize();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Watch cart item count for badge
|
|
final cartItemCount = ref.watch(cartItemCountProvider);
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
**Why in HomePage?**
|
|
- HomePage is the first screen after login
|
|
- Ensures cart is loaded early in app lifecycle
|
|
- Provides immediate cart count for navigation badge
|
|
|
|
### 3. Cart Badge Integration
|
|
**Location**: All pages with cart icon/badge
|
|
|
|
```dart
|
|
// Any page can watch cart count - it's always available
|
|
final cartItemCount = ref.watch(cartItemCountProvider);
|
|
|
|
// Display badge
|
|
if (cartItemCount > 0)
|
|
Badge(
|
|
label: Text('$cartItemCount'),
|
|
child: Icon(Icons.shopping_cart),
|
|
)
|
|
```
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
App Start
|
|
↓
|
|
HomePage mounts
|
|
↓
|
|
initState() calls cart.initialize()
|
|
↓
|
|
Cart loads from API → Syncs to Hive
|
|
↓
|
|
Cart state updates with items
|
|
↓
|
|
cartItemCountProvider updates
|
|
↓
|
|
All badges across app update reactively
|
|
↓
|
|
[keepAlive ensures state persists during navigation]
|
|
```
|
|
|
|
## API & Local Storage Integration
|
|
|
|
### Initialize Flow
|
|
1. **API First**: Fetch cart items from ERPNext API
|
|
2. **Product Details**: For each cart item, fetch full product data
|
|
3. **Calculate Conversions**: Apply business rules (boxes, m², etc.)
|
|
4. **Update State**: Set cart items with full product info
|
|
5. **Local Sync**: Automatically synced to Hive by repository
|
|
|
|
### Offline Fallback
|
|
- If API fails, cart loads from Hive cache
|
|
- All mutations queue for sync when online
|
|
- See `cart_repository_impl.dart` for sync logic
|
|
|
|
## Cart Operations
|
|
|
|
All cart operations work seamlessly after initialization:
|
|
|
|
```dart
|
|
// Add to cart (from any page)
|
|
await ref.read(cartProvider.notifier).addToCart(product, quantity: 2.0);
|
|
|
|
// Remove from cart
|
|
await ref.read(cartProvider.notifier).removeFromCart(productId);
|
|
|
|
// Update quantity
|
|
await ref.read(cartProvider.notifier).updateQuantity(productId, 5.0);
|
|
|
|
// Clear cart
|
|
await ref.read(cartProvider.notifier).clearCart();
|
|
```
|
|
|
|
All operations:
|
|
- Sync to API first
|
|
- Fallback to local on failure
|
|
- Queue for sync when offline
|
|
- Update UI reactively
|
|
|
|
## Testing Keep Alive
|
|
|
|
To verify keepAlive works:
|
|
|
|
1. **Navigate to HomePage** → Cart initializes
|
|
2. **Add items to cart** → Badge shows count
|
|
3. **Navigate to Products page** → Badge still shows count
|
|
4. **Navigate back to HomePage** → Cart state preserved, no re-fetch
|
|
5. **Navigate to Cart page** → Same items, no loading
|
|
6. **Hot restart app** → Cart reloads from API
|
|
|
|
## Performance Benefits
|
|
|
|
- **One-time API call**: Cart loads once on startup
|
|
- **No re-fetching**: Navigation doesn't trigger reloads
|
|
- **Instant updates**: All cart operations update state immediately
|
|
- **Offline support**: Hive cache provides instant fallback
|
|
- **Memory efficient**: Single provider instance for entire app
|
|
|
|
## Error Handling
|
|
|
|
If cart initialization fails:
|
|
- Error stored in `cartState.errorMessage`
|
|
- Can retry via `ref.read(cartProvider.notifier).initialize()`
|
|
- Cart page shows error state with retry button
|
|
- Local Hive cache used if available
|
|
|
|
## Related Files
|
|
|
|
- **Cart Provider**: `lib/features/cart/presentation/providers/cart_provider.dart`
|
|
- **Cart State**: `lib/features/cart/presentation/providers/cart_state.dart`
|
|
- **Data Providers**: `lib/features/cart/data/providers/cart_data_providers.dart`
|
|
- **Repository**: `lib/features/cart/data/repositories/cart_repository_impl.dart`
|
|
- **HomePage**: `lib/features/home/presentation/pages/home_page.dart`
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements:
|
|
- Add periodic background sync (every 5 minutes)
|
|
- Implement optimistic updates for faster UI
|
|
- Add cart merge logic when switching accounts
|
|
- Implement cart expiry (clear after 30 days)
|
|
- Add analytics tracking for cart events
|