This commit is contained in:
Phuoc Nguyen
2025-10-10 16:38:07 +07:00
parent e5b247d622
commit b94c158004
177 changed files with 25080 additions and 152 deletions

View File

@@ -0,0 +1,729 @@
# Riverpod 3.0 State Management - Complete Documentation
## Overview
This document provides comprehensive documentation for all Riverpod 3.0 providers in the Retail POS application. All providers use code generation with the `@riverpod` annotation.
---
## 1. Cart Management Providers
### Location: `/lib/features/home/presentation/providers/`
### 1.1 CartProvider (`cart_provider.dart`)
**Purpose**: Manages shopping cart state with full CRUD operations.
**Type**: `Notifier<List<CartItem>>`
**State**: `List<CartItem>`
**Methods**:
- `addItem(Product product, int quantity)` - Add product to cart or update quantity
- `removeItem(String productId)` - Remove item from cart
- `updateQuantity(String productId, int newQuantity)` - Update item quantity
- `incrementQuantity(String productId)` - Increase quantity by 1
- `decrementQuantity(String productId)` - Decrease quantity by 1
- `clearCart()` - Clear all items
- `containsProduct(String productId)` - Check if product exists
- `getProductQuantity(String productId)` - Get current quantity
**Usage**:
```dart
// Watch cart state
final cartItems = ref.watch(cartProvider);
// Add to cart
ref.read(cartProvider.notifier).addItem(product, 1);
// Update quantity
ref.read(cartProvider.notifier).updateQuantity('product-1', 3);
// Clear cart
ref.read(cartProvider.notifier).clearCart();
```
---
### 1.2 CartTotalProvider (`cart_total_provider.dart`)
**Purpose**: Calculates cart totals including subtotal, tax, and total.
**Type**: `Notifier<CartTotalData>`
**State**: `CartTotalData` (subtotal, tax, taxRate, total, itemCount)
**Dependencies**:
- `cartProvider` - For cart items
- `settingsProvider` - For tax rate
**Methods**:
- `applyDiscount(double discountAmount)` - Calculate total with flat discount
- `applyDiscountPercentage(double discountPercent)` - Calculate total with percentage discount
**Usage**:
```dart
// Watch cart totals
final totals = ref.watch(cartTotalProvider);
// Access values
print('Subtotal: ${totals.subtotal}');
print('Tax: ${totals.tax}');
print('Total: ${totals.total}');
print('Items: ${totals.itemCount}');
// Apply discount
final discounted = ref.read(cartTotalProvider.notifier).applyDiscount(10.0);
```
---
### 1.3 CartItemCountProvider (`cart_item_count_provider.dart`)
**Purpose**: Provides optimized cart item counts.
**Type**: Function provider (computed value)
**Providers**:
- `cartItemCount` - Total quantity of all items
- `cartUniqueItemCount` - Number of unique products
**Usage**:
```dart
// Total items quantity
final totalItems = ref.watch(cartItemCountProvider);
// Unique items count
final uniqueItems = ref.watch(cartUniqueItemCountProvider);
```
---
## 2. Products Management Providers
### Location: `/lib/features/products/presentation/providers/`
### 2.1 ProductLocalDataSourceProvider (`product_datasource_provider.dart`)
**Purpose**: Dependency injection for product data source.
**Type**: `Provider<ProductLocalDataSource>` (keepAlive)
**Usage**:
```dart
final dataSource = ref.read(productLocalDataSourceProvider);
```
---
### 2.2 ProductsProvider (`products_provider.dart`)
**Purpose**: Fetches and manages all products from Hive.
**Type**: `AsyncNotifier<List<Product>>`
**State**: `AsyncValue<List<Product>>`
**Methods**:
- `refresh()` - Refresh products from data source
- `syncProducts()` - Sync with remote API
- `getProductById(String id)` - Get specific product
**Usage**:
```dart
// Watch products
final productsAsync = ref.watch(productsProvider);
productsAsync.when(
data: (products) => ProductGrid(products: products),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
);
// Refresh products
await ref.read(productsProvider.notifier).refresh();
// Sync products
await ref.read(productsProvider.notifier).syncProducts();
```
---
### 2.3 SearchQueryProvider (`search_query_provider.dart`)
**Purpose**: Manages product search query state.
**Type**: `Notifier<String>`
**State**: `String` (search query)
**Methods**:
- `setQuery(String query)` - Update search query
- `clear()` - Clear search
- `isSearching` - Check if search is active
**Usage**:
```dart
// Watch search query
final query = ref.watch(searchQueryProvider);
// Update search
ref.read(searchQueryProvider.notifier).setQuery('laptop');
// Clear search
ref.read(searchQueryProvider.notifier).clear();
```
---
### 2.4 SelectedCategoryProvider (`selected_category_provider.dart`)
**Purpose**: Manages selected category filter.
**Type**: `Notifier<String?>`
**State**: `String?` (category ID or null for all)
**Methods**:
- `selectCategory(String? categoryId)` - Select category
- `clearSelection()` - Clear filter (show all)
- `hasSelection` - Check if category selected
- `isSelected(String categoryId)` - Check specific category
**Usage**:
```dart
// Watch selected category
final categoryId = ref.watch(selectedCategoryProvider);
// Select category
ref.read(selectedCategoryProvider.notifier).selectCategory('electronics');
// Clear filter
ref.read(selectedCategoryProvider.notifier).clearSelection();
```
---
### 2.5 FilteredProductsProvider (`filtered_products_provider.dart`)
**Purpose**: Combines search and category filtering.
**Type**: `Notifier<List<Product>>`
**State**: `List<Product>` (filtered results)
**Dependencies**:
- `productsProvider` - All products
- `searchQueryProvider` - Search query
- `selectedCategoryProvider` - Category filter
**Getters**:
- `availableCount` - Count of available products
- `outOfStockCount` - Count of out-of-stock products
**Usage**:
```dart
// Watch filtered products (automatically updates when filters change)
final filtered = ref.watch(filteredProductsProvider);
// Get counts
final available = ref.read(filteredProductsProvider.notifier).availableCount;
```
---
### 2.6 SortedProductsProvider (`filtered_products_provider.dart`)
**Purpose**: Sorts filtered products by various options.
**Type**: `Notifier<List<Product>>` with family parameter
**Parameters**: `ProductSortOption` enum
**Sort Options**:
- `nameAsc` - Name A-Z
- `nameDesc` - Name Z-A
- `priceAsc` - Price low to high
- `priceDesc` - Price high to low
- `newest` - Newest first
- `oldest` - Oldest first
**Usage**:
```dart
// Watch sorted products
final sorted = ref.watch(sortedProductsProvider(ProductSortOption.priceAsc));
```
---
## 3. Categories Management Providers
### Location: `/lib/features/categories/presentation/providers/`
### 3.1 CategoryLocalDataSourceProvider (`category_datasource_provider.dart`)
**Purpose**: Dependency injection for category data source.
**Type**: `Provider<CategoryLocalDataSource>` (keepAlive)
---
### 3.2 CategoriesProvider (`categories_provider.dart`)
**Purpose**: Fetches and manages all categories.
**Type**: `AsyncNotifier<List<Category>>`
**State**: `AsyncValue<List<Category>>`
**Methods**:
- `refresh()` - Refresh categories
- `syncCategories()` - Sync with remote API
- `getCategoryById(String id)` - Get category
- `getCategoryName(String id)` - Get category name
**Usage**:
```dart
// Watch categories
final categoriesAsync = ref.watch(categoriesProvider);
categoriesAsync.when(
data: (categories) => CategoryGrid(categories: categories),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
);
// Get category name
final name = ref.read(categoriesProvider.notifier).getCategoryName('electronics');
```
---
### 3.3 CategoryProductCountProvider (`category_product_count_provider.dart`)
**Purpose**: Calculates product count per category.
**Type**: Function provider with family parameter
**Providers**:
- `categoryProductCount(String categoryId)` - Count for specific category
- `allCategoryProductCounts` - Map of all counts
**Usage**:
```dart
// Watch count for specific category
final count = ref.watch(categoryProductCountProvider('electronics'));
// Watch all counts
final allCounts = ref.watch(allCategoryProductCountsProvider);
print('Electronics: ${allCounts['electronics']}');
```
---
## 4. Settings Management Providers
### Location: `/lib/features/settings/presentation/providers/`
### 4.1 SettingsLocalDataSourceProvider (`settings_datasource_provider.dart`)
**Purpose**: Dependency injection for settings data source.
**Type**: `Provider<SettingsLocalDataSource>` (keepAlive)
---
### 4.2 SettingsProvider (`settings_provider.dart`)
**Purpose**: Manages all app settings.
**Type**: `AsyncNotifier<AppSettings>` (keepAlive)
**State**: `AsyncValue<AppSettings>`
**Methods**:
- `updateThemeMode(ThemeMode mode)` - Update theme
- `updateLanguage(String language)` - Update language
- `updateTaxRate(double taxRate)` - Update tax rate
- `updateStoreName(String storeName)` - Update store name
- `updateCurrency(String currency)` - Update currency
- `toggleSync()` - Toggle sync on/off
- `updateLastSyncTime()` - Update sync timestamp
- `resetToDefaults()` - Reset all settings
**Usage**:
```dart
// Watch settings
final settingsAsync = ref.watch(settingsProvider);
// Update theme
await ref.read(settingsProvider.notifier).updateThemeMode(ThemeMode.dark);
// Update tax rate
await ref.read(settingsProvider.notifier).updateTaxRate(0.08);
// Reset settings
await ref.read(settingsProvider.notifier).resetToDefaults();
```
---
### 4.3 ThemeProvider (`theme_provider.dart`)
**Purpose**: Extracts theme-related data from settings.
**Type**: Function providers (computed values)
**Providers**:
- `themeModeProvider` - Current theme mode
- `isDarkModeProvider` - Check if dark mode
- `isLightModeProvider` - Check if light mode
- `isSystemThemeProvider` - Check if system theme
**Usage**:
```dart
// Watch theme mode
final theme = ref.watch(themeModeProvider);
// Check dark mode
final isDark = ref.watch(isDarkModeProvider);
// Use in MaterialApp
MaterialApp(
themeMode: ref.watch(themeModeProvider),
// ...
)
```
---
### 4.4 LanguageProvider (`language_provider.dart`)
**Purpose**: Manages language/locale settings.
**Type**: Function providers
**Providers**:
- `appLanguageProvider` - Current language code
- `supportedLanguagesProvider` - List of available languages
**Model**: `LanguageOption` (code, name, nativeName)
**Usage**:
```dart
// Watch current language
final language = ref.watch(appLanguageProvider);
// Get supported languages
final languages = ref.watch(supportedLanguagesProvider);
// Display language selector
DropdownButton(
value: language,
items: languages.map((lang) => DropdownMenuItem(
value: lang.code,
child: Text(lang.nativeName),
)).toList(),
onChanged: (code) => ref.read(settingsProvider.notifier).updateLanguage(code!),
)
```
---
## 5. Core Providers
### Location: `/lib/core/providers/`
### 5.1 NetworkInfoProvider (`network_info_provider.dart`)
**Purpose**: Network connectivity management.
**Type**: Multiple providers
**Providers**:
- `connectivityProvider` - Connectivity instance (keepAlive)
- `networkInfoProvider` - NetworkInfo implementation (keepAlive)
- `isConnectedProvider` - Check if connected
- `connectivityStreamProvider` - Stream of connectivity changes
**Usage**:
```dart
// Check if connected
final isConnected = await ref.read(isConnectedProvider.future);
// Watch connectivity changes
ref.listen(connectivityStreamProvider, (previous, next) {
next.when(
data: (connected) {
if (connected) {
print('Connected to internet');
} else {
print('Offline');
}
},
loading: () {},
error: (e, s) {},
);
});
```
---
### 5.2 SyncStatusProvider (`sync_status_provider.dart`)
**Purpose**: Manages data synchronization state.
**Type**: `AsyncNotifier<SyncResult>`
**State**: `AsyncValue<SyncResult>`
**Models**:
- `SyncResult` (status, lastSyncTime, message, error)
- `SyncState` enum (idle, syncing, success, failed, offline)
**Methods**:
- `syncAll()` - Sync all data (categories + products)
- `syncProducts()` - Sync only products
- `syncCategories()` - Sync only categories
- `resetStatus()` - Reset to idle state
**Getters**:
- `isSyncing` - Check if syncing
- `isSuccess` - Check if successful
- `isFailed` - Check if failed
- `isOffline` - Check if offline
- `isIdle` - Check if idle
**Additional Providers**:
- `lastSyncTimeProvider` - Get last sync time from settings
**Usage**:
```dart
// Watch sync status
final syncAsync = ref.watch(syncStatusProvider);
syncAsync.when(
data: (result) {
if (result.isSyncing) {
return CircularProgressIndicator();
} else if (result.isSuccess) {
return Text('Synced: ${result.lastSyncTime}');
} else if (result.isFailed) {
return Text('Error: ${result.message}');
}
return SyncButton();
},
loading: () => CircularProgressIndicator(),
error: (e, s) => ErrorWidget(e),
);
// Trigger sync
await ref.read(syncStatusProvider.notifier).syncAll();
// Sync only products
await ref.read(syncStatusProvider.notifier).syncProducts();
// Get last sync time
final lastSync = ref.watch(lastSyncTimeProvider);
```
---
## Provider Dependencies Graph
```
┌─────────────────────────────────────────────────────────────┐
│ CORE PROVIDERS │
├─────────────────────────────────────────────────────────────┤
│ - networkInfoProvider (keepAlive) │
│ - connectivityProvider (keepAlive) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SYNC STATUS PROVIDER │
├─────────────────────────────────────────────────────────────┤
│ Depends on: networkInfo, products, categories, settings │
└─────────────────────────────────────────────────────────────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────┐ ┌──────────────┐
│ PRODUCTS │ │ CATEGORIES │ │ SETTINGS │
│ - products │ │ - categories │ │ - settings │
│ (async) │ │ (async) │ │ (async) │
└──────────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────┐ ┌──────────────┐
│ FILTERED │ │ CATEGORY │ │ THEME │
│ PRODUCTS │ │ COUNTS │ │ LANGUAGE │
│ - search query │ │ - product │ │ - theme mode │
│ - selected cat │ │ counts │ │ - language │
│ - filtered list │ │ │ │ │
└──────────────────┘ └──────────────┘ └──────────────┘
┌──────────────────┐
│ CART │
│ - cart items │
│ - cart total │
│ - item count │
└──────────────────┘
```
---
## Code Generation
### Running Code Generator
```bash
# One-time build
dart run build_runner build --delete-conflicting-outputs
# Watch mode (auto-rebuild on changes)
dart run build_runner watch --delete-conflicting-outputs
# Clean and rebuild
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
```
### Generated Files
Each provider file with `@riverpod` annotation generates a corresponding `.g.dart` file:
- `cart_provider.dart``cart_provider.g.dart`
- `products_provider.dart``products_provider.g.dart`
- `categories_provider.dart``categories_provider.g.dart`
- etc.
---
## Best Practices
### 1. **Use .select() for Performance**
```dart
// Bad - rebuilds on any cart change
final cart = ref.watch(cartProvider);
// Good - rebuilds only when length changes
final itemCount = ref.watch(cartProvider.select((items) => items.length));
```
### 2. **Use AsyncValue Properly**
```dart
// Use .when() for simple cases
productsAsync.when(
data: (data) => Text('$data'),
loading: () => Loading(),
error: (e, s) => Error(e),
);
// Use pattern matching for complex cases
switch (productsAsync) {
case AsyncData(:final value):
return ProductList(value);
case AsyncError(:final error):
return ErrorWidget(error);
case AsyncLoading():
return LoadingWidget();
}
```
### 3. **Check ref.mounted in Async Operations**
```dart
Future<void> updateData() async {
state = const AsyncValue.loading();
await someAsyncOperation();
// Always check if still mounted
if (!ref.mounted) return;
state = AsyncValue.data(result);
}
```
### 4. **Use keepAlive for Dependencies**
```dart
// Dependency injection providers should be keepAlive
@Riverpod(keepAlive: true)
ProductDataSource productDataSource(Ref ref) {
return ProductDataSourceImpl();
}
```
### 5. **Invalidate vs Refresh**
```dart
// Invalidate - reset provider to initial state
ref.invalidate(productsProvider);
// Refresh - invalidate and immediately read
final products = ref.refresh(productsProvider);
```
---
## Testing Providers
### Unit Testing Example
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
test('Cart adds items correctly', () {
final container = ProviderContainer();
addTearDown(container.dispose);
// Initial state
expect(container.read(cartProvider), isEmpty);
// Add item
container.read(cartProvider.notifier).addItem(mockProduct, 1);
// Verify
expect(container.read(cartProvider).length, 1);
expect(container.read(cartItemCountProvider), 1);
});
test('Filtered products work correctly', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
// Wait for products to load
await container.read(productsProvider.future);
// Set search query
container.read(searchQueryProvider.notifier).setQuery('laptop');
// Check filtered results
final filtered = container.read(filteredProductsProvider);
expect(filtered.every((p) => p.name.toLowerCase().contains('laptop')), true);
});
}
```
---
## Summary
**Total Providers Created**: 25+
### By Feature:
- **Cart**: 3 providers
- **Products**: 5 providers
- **Categories**: 3 providers
- **Settings**: 4 providers
- **Core/Sync**: 5+ providers
### By Type:
- **AsyncNotifier**: 4 (products, categories, settings, sync)
- **Notifier**: 4 (cart, searchQuery, selectedCategory, filteredProducts)
- **Function Providers**: 10+ (counts, theme, language, etc.)
- **Dependency Injection**: 4 (data sources, network info)
All providers follow Riverpod 3.0 best practices with code generation!