> ## Documentation Index
> Fetch the complete documentation index at: https://lopiv2-invenicum-25.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# State Management

> Deep dive into state management with Provider in Invenicum

## 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`:

```dart theme={null}
runApp(
  MultiProvider(
    providers: [
      // 1. Services (singletons)
      // 2. State providers (ChangeNotifier)
    ],
    child: const MyApp(),
  ),
);
```

### Two-Tier Structure

#### Tier 1: Services (Provider)

Services are stateless singletons that handle API communication:

```dart theme={null}
// lib/main.dart:66-83
Provider<ApiService>(create: (c) => ApiService()),
Provider(create: (c) => PluginService(c.read<ApiService>())),
Provider(create: (c) => DashboardService(c.read<ApiService>())),
Provider(create: (c) => InventoryItemService(c.read<ApiService>())),
// ... more services
```

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

```dart theme={null}
// lib/main.dart:88-260
ChangeNotifierProvider<AuthProvider>.value(value: authProvider),
ChangeNotifierProxyProvider<AuthProvider, InventoryItemProvider>(...),
ChangeNotifierProxyProvider<AuthProvider, ThemeProvider>(...),
// ... more providers
```

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

```dart theme={null}
// lib/main.dart:88
ChangeNotifierProvider<AuthProvider>.value(value: authProvider)
```

**Example**: `AuthProvider` (`lib/providers/auth_provider.dart`)

```dart theme={null}
class AuthProvider with ChangeNotifier {
  UserData? _user;
  String? _token;
  bool _isLoading = true;
  
  bool get isAuthenticated => _user != null && _token != null;
  
  Future<LoginResponse> login(String username, String password) async {
    _isLoading = true;
    notifyListeners();  // UI shows loading state
    
    try {
      final response = await _apiService.login(username, password);
      if (response.success && response.token != null) {
        _token = response.token;
        _user = response.user;
      }
      return response;
    } finally {
      _isLoading = false;
      notifyListeners();  // UI updates with result
    }
  }
}
```

### 2. ChangeNotifierProxyProvider

Used when a provider depends on another provider's state:

```dart theme={null}
// lib/main.dart:171-185
ChangeNotifierProxyProvider<AuthProvider, InventoryItemProvider>(
  create: (c) => InventoryItemProvider(
    c.read<InventoryItemService>(),
    c.read<AssetPrintService>(),
  ),
  update: (context, auth, prev) {
    // Reactive logic: when auth state changes, this runs
    if (!auth.isLoading && auth.isAuthenticated && auth.token != null) {
      if (prev != null && !prev.isLoading) {
        Future.microtask(() => prev.loadAllItemsGlobal());
      }
    }
    return prev!;
  },
)
```

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

```dart theme={null}
class ExampleProvider with ChangeNotifier {
  bool _isLoading = false;
  bool get isLoading => _isLoading;
  
  Future<void> loadData() async {
    _isLoading = true;
    notifyListeners();  // Show loading indicator
    
    try {
      // Fetch data
    } catch (e) {
      // Handle error
      rethrow;  // Let UI show error message
    } finally {
      _isLoading = false;
      notifyListeners();  // Hide loading indicator
    }
  }
}
```

**Example**: `lib/providers/inventory_item_provider.dart:292-346`

### Pattern 2: Data Caching

Providers cache data to minimize API calls:

```dart theme={null}
// lib/providers/inventory_item_provider.dart:64-68
final Map<String, InventoryResponse> _itemsCache = {};

Future<void> loadInventoryItems({
  required int containerId,
  required int assetTypeId,
  bool forceReload = false,
}) async {
  final key = _getCacheKey(containerId, assetTypeId);
  
  // Return cached data if available
  if (_itemsCache.containsKey(key) && !forceReload) {
    _recalculateTotalsAndNotify();
    return;
  }
  
  // Otherwise fetch from API
  _isLoading = true;
  notifyListeners();
  
  try {
    final response = await _itemService.fetchInventoryItems(...);
    _itemsCache[key] = response;
  } finally {
    _isLoading = false;
    _recalculateTotalsAndNotify();
  }
}
```

### Pattern 3: Computed Getters

Providers expose processed data through getters:

```dart theme={null}
// lib/providers/inventory_item_provider.dart:181-216
List<InventoryItem> get inventoryItems {
  final cId = _currentContainerId;
  final atId = _currentAssetTypeId;
  final key = _getCacheKey(cId, atId);
  final response = _itemsCache[key];
  
  if (response == null) return [];
  
  // Filter, sort, paginate
  Iterable<InventoryItem> processedItems = _applyFilters(response.items);
  List<InventoryItem> sortedList = processedItems.toList();
  _applySort(sortedList);
  
  // Pagination
  final startIndex = (_currentPage - 1) * _itemsPerPage;
  final endIndex = startIndex + _itemsPerPage;
  
  return sortedList.sublist(startIndex, endIndex);
}
```

**UI usage**:

```dart theme={null}
// Widgets automatically rebuild when inventoryItems changes
final items = context.watch<InventoryItemProvider>().inventoryItems;
```

### Pattern 4: Auth-Dependent Initialization

Many providers auto-load data when the user logs in:

```dart theme={null}
// lib/main.dart:154-168
ChangeNotifierProxyProvider<AuthProvider, DashboardProvider>(
  create: (context) => DashboardProvider(context.read<DashboardService>()),
  update: (context, auth, previous) {
    if (!auth.isLoading && auth.isAuthenticated) {
      // Only load if we haven't loaded yet
      if (previous != null && previous.stats == null && !previous.isLoading) {
        Future.microtask(() => previous.fetchStats());
      }
    }
    return previous!;
  },
)
```

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

```dart theme={null}
UserData? _user;
String? _token;
bool _isLoading = true;
```

### 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**:

```dart theme={null}
Map<String, InventoryResponse> _itemsCache = {};  // Cached data
Map<String, String> _filters = {};                // Active filters
String? _globalSearchTerm;                        // Search query
int _currentPage = 1;                             // Pagination
int _itemsPerPage = 10;
bool _isLoading = false;
```

**Computed getter**:

```dart theme={null}
List<InventoryItem> get inventoryItems {}
```

### 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**:

```dart theme={null}
// lib/main.dart:219-231
ChangeNotifierProxyProvider<AuthProvider, ContainerProvider>(
  create: (c) => ContainerProvider(
    c.read<ContainerService>(),
    c.read<AssetTypeService>(),
    c.read<LocationService>(),
  ),
  update: (context, auth, prev) {
    if (auth.isAuthenticated && auth.token != null && !auth.isLoading) {
      Future.microtask(() => prev?.loadContainers());
    }
    return prev!;
  },
)
```

### 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**:

```dart theme={null}
List<StorePlugin> _installed = [];
List<StorePlugin> _community = [];
bool _isLoading = false;
UserData? _currentUser;  // For UI template processing
```

### 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**:

```dart theme={null}
// lib/main.dart:188-203
ChangeNotifierProxyProvider<AuthProvider, ThemeProvider>(
  create: (c) => ThemeProvider(c.read<ThemeService>()),
  update: (context, auth, prev) {
    if (auth.isAuthenticated && auth.user?.themeConfig != null && !prev!.isInitialized) {
      final config = auth.user!.themeConfig!;
      prev.setInitializing();
      prev.initializeThemeFromConfig(
        config.theme.primaryColor,
        config.theme.brightness,
      );
    }
    return prev!;
  },
)
```

### PreferencesProvider

**File**: `lib/providers/preferences_provider.dart`

**Responsibilities**:

* Manage user preferences (language, currency, notifications)
* Load preferences on login
* Persist preference changes

**Usage in app**:

```dart theme={null}
// lib/main.dart:292-304
final preferencesProvider = context.watch<PreferencesProvider>();

MaterialApp.router(
  locale: preferencesProvider.locale,
  // ...
)
```

## Accessing Providers in UI

### watch vs read vs select

```dart theme={null}
// 1. watch - Rebuilds when provider changes
final items = context.watch<InventoryItemProvider>().inventoryItems;

// 2. read - One-time access (no rebuild)
context.read<InventoryItemProvider>().createInventoryItem(item);

// 3. select - Rebuild only when specific property changes
final isLoading = context.select<InventoryItemProvider, bool>(
  (provider) => provider.isLoading,
);
```

### Consumer Widget

For granular rebuilds:

```dart theme={null}
Consumer<InventoryItemProvider>(
  builder: (context, provider, child) {
    if (provider.isLoading) {
      return CircularProgressIndicator();
    }
    return ListView(children: provider.inventoryItems.map(...));
  },
)
```

### Selector Widget

For optimal performance (rebuild only on specific changes):

```dart theme={null}
Selector<InventoryItemProvider, List<InventoryItem>>(
  selector: (context, provider) => provider.inventoryItems,
  builder: (context, items, child) {
    return ListView(children: items.map(...));
  },
)
```

## State Update Flow

Typical flow for updating state:

```
1. User Action (Button Press)
   ↓
2. Widget calls provider method
   context.read<ExampleProvider>().updateData(...)
   ↓
3. Provider updates internal state
   _data = newData;
   ↓
4. Provider calls service
   await _service.updateOnServer(newData);
   ↓
5. Provider notifies listeners
   notifyListeners();
   ↓
6. Widgets rebuild
   context.watch<ExampleProvider>().data rebuilds UI
```

**Example**: Updating an inventory item (`lib/providers/inventory_item_provider.dart:403-432`)

```dart theme={null}
Future<InventoryItem> updateAssetWithFiles(
  InventoryItem updatedItem, {
  FileData filesToUpload = const [],
  List<int> imageIdsToDelete = const [],
}) async {
  _isLoading = true;
  notifyListeners();  // Step 3: Show loading
  
  try {
    // Step 4: API call
    final result = await _itemService.updateInventoryItem(
      updatedItem,
      filesToUpload: filesToUpload,
      imageIdsToDelete: imageIdsToDelete,
    );
    
    // Step 5: Refresh cache
    await loadInventoryItems(
      containerId: updatedItem.containerId,
      assetTypeId: updatedItem.assetTypeId,
      forceReload: true,
    );
    
    return result;  // Return fresh data to UI
  } catch (e) {
    _isLoading = false;
    notifyListeners();
    rethrow;  // Let UI handle error
  }
}
```

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

```dart theme={null}
// lib/providers/inventory_item_provider.dart:76-84
bool _isDisposed = false;

@override
void dispose() {
  _isDisposed = true;
  super.dispose();
}

@override
void notifyListeners() {
  if (!_isDisposed) super.notifyListeners();
}
```

### 3. Avoid Unnecessary Rebuilds

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

```dart theme={null}
// Only rebuilds when isLoading changes, not on every provider change
final isLoading = context.select<ExampleProvider, bool>(
  (p) => p.isLoading,
);
```

### 4. Error Handling

Rethrow errors for UI to display:

```dart theme={null}
try {
  await _service.riskyOperation();
} catch (e) {
  _isLoading = false;
  notifyListeners();
  rethrow;  // UI can show SnackBar/Toast
}
```

### 5. Use Future.microtask for Initialization

Avoid calling async methods during build:

```dart theme={null}
// ❌ BAD - Throws error
update: (context, auth, prev) {
  prev.loadData();  // Can't call async during build
  return prev;
}

// ✅ GOOD
update: (context, auth, prev) {
  Future.microtask(() => prev.loadData());
  return prev;
}
```

### 6. Optimize with Getters

Compute data in getters, not in methods:

```dart theme={null}
// ✅ GOOD - Computed on access
List<Item> get filteredItems => _items.where((i) => i.active).toList();

// ❌ BAD - Manual call required
void filterItems() {
  _filteredItems = _items.where((i) => i.active).toList();
  notifyListeners();
}
```

## Testing Providers

Providers are easy to test in isolation:

```dart theme={null}
test('AuthProvider login success', () async {
  final mockApiService = MockApiService();
  final provider = AuthProvider(apiService: mockApiService);
  
  when(mockApiService.login(any, any)).thenAnswer(
    (_) async => LoginResponse(success: true, token: 'abc123'),
  );
  
  final result = await provider.login('user', 'pass');
  
  expect(result.success, true);
  expect(provider.token, 'abc123');
  expect(provider.isAuthenticated, true);
});
```

## Next Steps

* [Architecture Overview](https://lopiv2-invenicum-25.mintlify.site/development/architecture.md) - Understand the bigger picture
* [Project Structure](https://lopiv2-invenicum-25.mintlify.site/development/project-structure.md) - Navigate the codebase
* [Contributing Guide](https://lopiv2-invenicum-25.mintlify.site/development/contributing.md) - Extend the app with new functionality


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.