Skip to main content

Overview

Invenicum uses the Provider package for state management, implementing the ChangeNotifier pattern. This approach provides:
  • Reactive UI updates
  • Clear separation of business logic and presentation
  • Dependency injection
  • Testability

Provider Hierarchy

The complete provider tree is established in lib/main.dart:64-261:

Two-Tier Structure

Tier 1: Services (Provider)

Services are stateless singletons that handle API communication:
Key characteristics:
  • No reactive state (don’t call notifyListeners())
  • Injected via constructor dependency
  • Single instance shared across the app

Tier 2: State Providers (ChangeNotifier)

Providers manage reactive state:
Key characteristics:
  • Extend ChangeNotifier
  • Call notifyListeners() to trigger UI rebuilds
  • Can depend on other providers (via ProxyProvider)

Provider Types

1. ChangeNotifierProvider

Used for independent state that doesn’t depend on other providers:
Example: AuthProvider (lib/providers/auth_provider.dart)

2. ChangeNotifierProxyProvider

Used when a provider depends on another provider’s state:
Pattern explanation:
  • create: Instantiates the provider once
  • update: Called whenever AuthProvider changes
  • auth: Current state of the dependency (AuthProvider)
  • prev: The existing instance of InventoryItemProvider
  • Returns prev! to maintain the same instance (preserves cache)

Common Provider Patterns

Pattern 1: Loading State

All providers follow this pattern for async operations:
Example: lib/providers/inventory_item_provider.dart:292-346

Pattern 2: Data Caching

Providers cache data to minimize API calls:

Pattern 3: Computed Getters

Providers expose processed data through getters:
UI usage:

Pattern 4: Auth-Dependent Initialization

Many providers auto-load data when the user logs in:
Key points:
  • Checks !auth.isLoading to ensure auth is ready
  • Checks auth.isAuthenticated to ensure user is logged in
  • Uses Future.microtask() to avoid calling async code during build
  • Prevents redundant loads with previous.stats == null

Example Providers

AuthProvider

File: lib/providers/auth_provider.dart Responsibilities:
  • Manage authentication state (user, token, loading)
  • Login/logout operations
  • Profile updates
  • GitHub OAuth integration
  • Password management
Key methods:
  • login() - Authenticate user
  • logout() - Clear session
  • updateProfile() - Update user data
  • checkAuthStatus() - Restore session on app start
State:

InventoryItemProvider

File: lib/providers/inventory_item_provider.dart Responsibilities:
  • Load and cache inventory items
  • Filter, sort, paginate items (client-side)
  • CRUD operations (create, update, delete, clone)
  • Manage current container/asset type context
  • Track price history and market value
Key methods:
  • loadInventoryItems() - Fetch items for a specific asset type
  • loadAllItemsGlobal() - Load all items across containers
  • createInventoryItem() - Add new item
  • updateAssetWithFiles() - Update item with file uploads
  • deleteInventoryItem() - Remove item
  • setFilter() - Apply search/filter
  • sortInventoryItems() - Sort by column
  • goToPage() - Navigate pagination
State:
Computed getter:

ContainerProvider

File: lib/providers/container_provider.dart Responsibilities:
  • Load container hierarchy
  • Manage asset types within containers
  • Load data lists (dropdowns)
  • CRUD for containers, asset types, locations
Dependency injection:

PluginProvider

File: lib/providers/plugin_provider.dart Responsibilities:
  • Manage installed plugins
  • Browse community plugins (GitHub + database)
  • Install/uninstall plugins
  • Activate/deactivate plugins
  • Process plugin UI (STAC) with user context
Key methods:
  • refresh() - Reload plugin lists
  • install() - Install from marketplace
  • uninstall() - Remove plugin
  • togglePluginStatus() - Enable/disable
  • getProcessedUi() - Inject user data into plugin UI (e.g., {{userName}})
State:

ThemeProvider

File: lib/providers/theme_provider.dart Responsibilities:
  • Manage theme customization (colors, brightness)
  • Sync with user profile theme config
  • Persist theme changes to backend
Initialization:

PreferencesProvider

File: lib/providers/preferences_provider.dart Responsibilities:
  • Manage user preferences (language, currency, notifications)
  • Load preferences on login
  • Persist preference changes
Usage in app:

Accessing Providers in UI

watch vs read vs select

Consumer Widget

For granular rebuilds:

Selector Widget

For optimal performance (rebuild only on specific changes):

State Update Flow

Typical flow for updating state:
Example: Updating an inventory item (lib/providers/inventory_item_provider.dart:403-432)

Best Practices

1. Single Responsibility

Each provider manages one domain:
  • ✅ AuthProvider handles auth, InventoryItemProvider handles items
  • ❌ Don’t mix concerns (e.g., auth logic in InventoryItemProvider)

2. Dispose Properly

Always dispose of resources:

3. Avoid Unnecessary Rebuilds

Use context.select() or Selector for expensive widgets:

4. Error Handling

Rethrow errors for UI to display:

5. Use Future.microtask for Initialization

Avoid calling async methods during build:

6. Optimize with Getters

Compute data in getters, not in methods:

Testing Providers

Providers are easy to test in isolation:

Next Steps