11 KiB
Cart API Integration - Complete Implementation
Overview
This document describes the complete cart API integration following clean architecture principles for the Worker Flutter app.
Architecture
Presentation Layer (UI)
↓
Domain Layer (Business Logic)
↓
Data Layer (API + Local Storage)
Files Created
1. API Constants
File: /Users/ssg/project/worker/lib/core/constants/api_constants.dart
Added Endpoints:
addToCart- POST /api/method/building_material.building_material.api.user_cart.add_to_cartremoveFromCart- POST /api/method/building_material.building_material.api.user_cart.remove_from_cartgetUserCart- POST /api/method/building_material.building_material.api.user_cart.get_user_cart
2. Domain Repository Interface
File: /Users/ssg/project/worker/lib/features/cart/domain/repositories/cart_repository.dart
Methods:
addToCart()- Add items to cartremoveFromCart()- Remove items from cartgetCartItems()- Get all cart itemsupdateQuantity()- Update item quantityclearCart()- Clear all cart itemsgetCartTotal()- Get total cart valuegetCartItemCount()- Get total item count
Returns: Domain entities (CartItem), not models.
3. Remote Data Source
File: /Users/ssg/project/worker/lib/features/cart/data/datasources/cart_remote_datasource.dart
Class: CartRemoteDataSourceImpl
Features:
- Uses
DioClientfor HTTP requests - Proper error handling with custom exceptions
- Converts Dio exceptions to app-specific exceptions
- Maps API response to
CartItemModel
API Request/Response Mapping:
Add to Cart
// Request
{
"items": [
{
"item_id": "Gạch ốp Signature SIG.P-8806",
"amount": 4000000,
"quantity": 33
}
]
}
// Response
{
"message": [
{
"item_id": "Gạch ốp Signature SIG.P-8806",
"success": true,
"message": "Updated quantity in cart"
}
]
}
Get Cart Items
// Request
{
"limit_start": 0,
"limit_page_length": 0
}
// Response
{
"message": [
{
"name": "rfsbgqusrj",
"item": "Gạch ốp Signature SIG.P-8806",
"quantity": 33.0,
"amount": 4000000.0,
"item_code": "Gạch ốp Signature SIG.P-8806",
"item_name": "Gạch ốp Signature SIG.P-8806",
"image": null,
"conversion_of_sm": 0.0
}
]
}
Error Handling:
TimeoutException- Connection/send/receive timeoutNoInternetException- No network connectionUnauthorizedException- 401 errorsForbiddenException- 403 errorsNotFoundException- 404 errorsRateLimitException- 429 errorsServerException- 5xx errorsNetworkException- Other network errorsParseException- Invalid response format
4. Local Data Source
File: /Users/ssg/project/worker/lib/features/cart/data/datasources/cart_local_datasource.dart
Class: CartLocalDataSourceImpl
Features:
- Uses Hive for local storage
- Box name:
cartBox(fromHiveBoxNames.cartBox) - Uses
Box<dynamic>with.whereType<CartItemModel>()(best practice) - Stores items with
productIdas key
Methods:
saveCartItems()- Replace all cart itemsgetCartItems()- Get all cart itemsaddCartItem()- Add single item (merges if exists)updateCartItem()- Update single itemremoveCartItems()- Remove items by productIdclearCart()- Clear all itemsgetCartItemCount()- Sum of all quantitiesgetCartTotal()- Sum of all subtotals
Best Practice:
Box<dynamic> get _cartBox => _hiveService.getBox<dynamic>(HiveBoxNames.cartBox);
Future<List<CartItemModel>> getCartItems() async {
final items = _cartBox.values
.whereType<CartItemModel>()
.toList();
return items;
}
5. Repository Implementation
File: /Users/ssg/project/worker/lib/features/cart/data/repositories/cart_repository_impl.dart
Class: CartRepositoryImpl
Strategy: API-first with local fallback
Workflow:
-
Add to Cart:
- Try API request first
- On success: Sync to local storage
- On network error: Add to local only + queue for sync
- Convert models to entities before returning
-
Remove from Cart:
- Try API request first
- On success: Remove from local storage
- On network error: Remove from local only + queue for sync
-
Get Cart Items:
- Try API request first
- On success: Sync to local storage
- On network error: Return local data (offline support)
- Convert models to entities before returning
-
Update Quantity:
- Uses
addToCartwith new quantity (replaces existing)
- Uses
-
Clear Cart:
- Gets all items and removes them via API
Error Propagation:
- Rethrows:
StorageException,NetworkException,ServerException,ValidationException - Wraps unknown errors in
UnknownException
Helper Methods:
_modelToEntity()- ConvertCartItemModeltoCartItementity_createCartItemModel()- Create new model from parameters
6. Riverpod Providers
Remote Data Source Provider:
@riverpod
CartRemoteDataSource cartRemoteDataSource(CartRemoteDataSourceRef ref) {
final dioClient = ref.watch(dioClientProvider).requireValue;
return CartRemoteDataSourceImpl(dioClient);
}
Local Data Source Provider:
@riverpod
CartLocalDataSource cartLocalDataSource(CartLocalDataSourceRef ref) {
final hiveService = HiveService();
return CartLocalDataSourceImpl(hiveService);
}
Repository Provider:
@riverpod
CartRepository cartRepository(CartRepositoryRef ref) {
final remoteDataSource = ref.watch(cartRemoteDataSourceProvider);
final localDataSource = ref.watch(cartLocalDataSourceProvider);
return CartRepositoryImpl(
remoteDataSource: remoteDataSource,
localDataSource: localDataSource,
);
}
Usage Example
In Cart Provider (Presentation Layer)
@riverpod
class Cart extends _$Cart {
CartRepository get _repository => ref.read(cartRepositoryProvider);
// Add product to cart via API
Future<void> addProductToCart(Product product, double quantity) async {
try {
// Call repository
final items = await _repository.addToCart(
itemIds: [product.erpnextItemCode ?? product.productId],
quantities: [quantity],
prices: [product.basePrice],
);
// Update UI state
state = state.copyWith(items: _convertToCartItemData(items));
} catch (e) {
// Handle error (show snackbar, etc.)
throw e;
}
}
// Get cart items from API
Future<void> loadCartItems() async {
try {
final items = await _repository.getCartItems();
state = state.copyWith(items: _convertToCartItemData(items));
} catch (e) {
// Handle error
throw e;
}
}
// Remove from cart via API
Future<void> removeProductFromCart(String productId) async {
try {
await _repository.removeFromCart(itemIds: [productId]);
// Update UI state
final updatedItems = state.items
.where((item) => item.product.productId != productId)
.toList();
state = state.copyWith(items: updatedItems);
} catch (e) {
// Handle error
throw e;
}
}
}
API-First Strategy Details
Online Scenario:
- User adds item to cart
- API request sent to backend
- Backend returns updated cart
- Local storage synced with API response
- UI updated with latest data
Offline Scenario:
- User adds item to cart
- API request fails (no internet)
- Item added to local storage only
- Request queued for later sync (TODO)
- UI updated from local data
Sync on Reconnection:
When internet connection restored:
- Process offline queue
- Send queued requests to API
- Sync local storage with API responses
- Clear queue on success
Important Notes
Product ID Mapping
- Frontend: Uses
product.productId(UUID) - API: Expects
item_id(ERPNext item code) - Mapping: Use
product.erpnextItemCodewhen calling API - Fallback: If
erpnextItemCodeis null, useproductId
API Response Fields
name- Cart item ID (ERPNext internal ID)itemoritem_code- Product ERPNext codequantity- Item quantityamount- Unit priceconversion_of_sm- Conversion factor (if applicable)
Local Storage
- Box:
HiveBoxNames.cartBox - Key:
productId(for easy lookup and updates) - Type:
CartItemModel(Hive type ID: 5) - Strategy:
Box<dynamic>with.whereType<CartItemModel>()
Error Handling Best Practices
- Always catch specific exceptions first
- Rethrow domain-specific exceptions
- Wrap unknown errors in
UnknownException - Provide user-friendly error messages
- Log errors for debugging
Testing Checklist
- Add single item to cart
- Add multiple items at once
- Update item quantity
- Remove single item
- Remove multiple items
- Clear entire cart
- Get cart items
- Calculate cart total
- Calculate item count
- Offline add (no internet)
- Offline remove (no internet)
- Sync after reconnection
- Handle API errors gracefully
- Handle timeout errors
- Handle unauthorized errors
- Local storage persistence
Future Enhancements
-
Offline Queue System:
- Implement request queue for offline operations
- Auto-sync when connection restored
- Conflict resolution for concurrent edits
-
Optimistic Updates:
- Update UI immediately
- Sync with backend in background
- Rollback on failure
-
Cart Sync Status:
- Track sync state per item
- Show sync indicators in UI
- Manual sync trigger
-
Multi-cart Support:
- Named carts (e.g., "Project A", "Project B")
- Switch between carts
- Merge carts
-
Cart Analytics:
- Track add/remove events
- Cart abandonment tracking
- Conversion metrics
Related Files
- Domain Entity:
/Users/ssg/project/worker/lib/features/cart/domain/entities/cart_item.dart - Data Model:
/Users/ssg/project/worker/lib/features/cart/data/models/cart_item_model.dart - UI State:
/Users/ssg/project/worker/lib/features/cart/presentation/providers/cart_state.dart - Cart Page:
/Users/ssg/project/worker/lib/features/cart/presentation/pages/cart_page.dart
Dependencies
dio- HTTP clienthive_ce- Local databaseriverpod- State managementriverpod_annotation- Code generation
Code Generation
To generate Riverpod providers (.g.dart files):
dart run build_runner build --delete-conflicting-outputs
Or for watch mode:
dart run build_runner watch --delete-conflicting-outputs
Summary
This implementation provides:
- ✅ Clean architecture separation
- ✅ API-first with local fallback
- ✅ Offline support
- ✅ Proper error handling
- ✅ Type-safe operations
- ✅ Hive best practices
- ✅ Riverpod integration
- ✅ Scalable and maintainable code
All files follow the existing codebase patterns and are ready for integration with the UI layer.