730 lines
20 KiB
Markdown
730 lines
20 KiB
Markdown
# 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!
|