Files
retail/docs/PROVIDERS_DOCUMENTATION.md
Phuoc Nguyen b94c158004 runable
2025-10-10 16:38:07 +07:00

20 KiB

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:

// 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:

// 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:

// 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:

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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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

# 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.dartcart_provider.g.dart
  • products_provider.dartproducts_provider.g.dart
  • categories_provider.dartcategories_provider.g.dart
  • etc.

Best Practices

1. Use .select() for Performance

// 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

// 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

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

// Dependency injection providers should be keepAlive
@Riverpod(keepAlive: true)
ProductDataSource productDataSource(Ref ref) {
  return ProductDataSourceImpl();
}

5. Invalidate vs Refresh

// Invalidate - reset provider to initial state
ref.invalidate(productsProvider);

// Refresh - invalidate and immediately read
final products = ref.refresh(productsProvider);

Testing Providers

Unit Testing Example

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!