Architecture Philosophy
Invenicum follows a layered architecture pattern that separates concerns into distinct layers:- Presentation Layer: UI components (Screens & Widgets)
- Business Logic Layer: State management (Providers)
- Data Layer: Services and Models
- Infrastructure Layer: Routing, localization, configuration
Core Architecture Patterns
Three-Layer Architecture
Provider Pattern for State Management
Invenicum uses the Provider package for state management, implementing:ChangeNotifierfor reactive state updatesChangeNotifierProviderfor independent stateChangeNotifierProxyProviderfor dependent state (e.g., auth-dependent providers)
lib/main.dart:64-261, where services are initialized first, followed by state providers.
Service Layer Pattern
All backend communication is abstracted through service classes:- ApiService (
lib/data/services/api_service.dart): Base HTTP client with Dio - Domain Services: Specialized services for each domain (InventoryItemService, ContainerService, etc.)
Provider<T> (not ChangeNotifierProvider) since they don’t manage reactive state.
Key Architectural Components
1. API Client Architecture
TheApiService class uses the singleton pattern and provides:
- Synchronous token caching for performance (
_cachedToken) - Dio interceptors for automatic auth header injection
- Global error handling (401 auto-logout)
- Configurable timeouts via
Environmentconfig
2. Authentication Flow
lib/providers/auth_provider.dart:293-319
- User credentials sent to ApiService
- Token cached in memory and persisted to SharedPreferences
refreshListenableon GoRouter triggers route guards- ChangeNotifierProxyProviders react to auth state changes
3. Routing Architecture
Invenicum uses GoRouter with declarative routing:- Auth-based redirects
- Deep linking support (QR codes)
- ShellRoute for consistent layout
- Path parameter extraction for nested resources
4. Plugin System Architecture
The plugin system allows dynamic UI injection using STAC (Server-Driven UI):lib/data/services/plugin_service.dart: API integrationlib/providers/plugin_provider.dart: Plugin state managementlib/core/utils/sdk_plugin_parser.dart: STAC action parser
StacSlot (e.g., inventory_header in lib/widgets/layout/main_layout.dart:340)
5. State Dependency Chain
Many providers depend on authentication state:- Data loads only when authenticated
- Prevents redundant API calls
- Automatic cleanup on logout
Data Flow Example
Let’s trace a typical operation: Loading inventory items- Screen:
lib/screens/assets/asset_list_screen.dart - Provider:
lib/providers/inventory_item_provider.dart:292-346 - Service:
lib/data/services/inventory_item_service.dart
Component Interactions
MainLayout Integration
TheMainLayout widget (lib/widgets/layout/main_layout.dart) demonstrates full-stack integration:
lib/widgets/layout/main_layout.dart:61-107):
- Parse URL on layout mount
- Extract container/asset type IDs
- Pre-load data into providers
- UI renders with data ready
Configuration & Environment
Configuration is centralized inlib/config/environment.dart:
Localization Architecture
Invenicum supports multiple languages using Flutter’s built-in l10n:- Definitions:
lib/l10n/(ARB files) - Access:
AppLocalizations.of(context) - Locale management:
PreferencesProvider - Configuration:
lib/main.dart:298-304
Best Practices
- Separation of Concerns: Keep UI logic in widgets, business logic in providers, API calls in services
- Single Responsibility: Each provider manages one domain (auth, inventory, containers, etc.)
- Dependency Injection: Services injected via Provider, not instantiated in widgets
- Reactive State: Use
context.watch<T>()for reactive rebuilds,context.read<T>()for one-time access - Error Handling: Catch exceptions in providers, rethrow to UI for user feedback
- Memory Management: Dispose controllers, prevent memory leaks in providers
Next Steps
- Project Structure - Detailed directory organization
- State Management - Deep dive into Provider patterns
- Project Structure - Understanding the codebase organization
