# 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>` **State**: `List` **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` **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` (keepAlive) **Usage**: ```dart final dataSource = ref.read(productLocalDataSourceProvider); ``` --- ### 2.2 ProductsProvider (`products_provider.dart`) **Purpose**: Fetches and manages all products from Hive. **Type**: `AsyncNotifier>` **State**: `AsyncValue>` **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` **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` **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>` **State**: `List` (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>` 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` (keepAlive) --- ### 3.2 CategoriesProvider (`categories_provider.dart`) **Purpose**: Fetches and manages all categories. **Type**: `AsyncNotifier>` **State**: `AsyncValue>` **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` (keepAlive) --- ### 4.2 SettingsProvider (`settings_provider.dart`) **Purpose**: Manages all app settings. **Type**: `AsyncNotifier` (keepAlive) **State**: `AsyncValue` **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` **State**: `AsyncValue` **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 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!