State Management (NgRx)
Audience: End users and non-technical stakeholders Last Updated: 2026-04-05 Version: 2.0.0
Overview
Llamafin uses NgRx, a powerful state management library inspired by Redux, to maintain a single, consistent source of truth for the entire application. Every piece of data -- from your authentication status and music library to playback state and UI preferences -- flows through a predictable, unidirectional data pipeline.
This architecture ensures that the app is reliable, testable, and debuggable. Changes to the app happen in one direction only, making it easier to understand what happened, why it happened, and how to recover if something goes wrong.
Key Concepts
1. Single Source of Truth
All application data lives in one unified state tree. Think of this as a comprehensive snapshot of everything the app knows at any given moment:
- Who you are: Authentication status, user profile, server connection
- What you're playing: Current track, queue, shuffle/repeat settings
- What you've downloaded: Offline content, download progress, quality settings
- How the app looks: Theme, font, visualizer, player view preferences
- What the device is doing: Network status, orientation, performance metrics
This unified state ensures that every part of the app sees the same data, eliminating inconsistencies between screens.
2. Unidirectional Data Flow
Data in Llamafin flows in one direction only:
User Action → Action Dispatched → State Updated → UI Refreshed
| Step | Description | Example |
|---|---|---|
| 1. User Action | You tap a button, scroll, or type | Tap "Play" on a song |
| 2. Action Dispatched | The app broadcasts a description of what happened | Play Track Requested |
| 3. State Updated | Pure logic processes the action and produces new state | Queue updated, track loaded |
| 4. UI Refreshed | All affected screens update automatically | Player shows new track, mini-player updates |
This one-way flow eliminates hidden side effects and makes the app's behaviour predictable.
3. Feature Slices
The state tree is divided into 45+ feature slices, each responsible for a specific domain:
| Category | Feature Slices |
|---|---|
| Core | App lifecycle, authentication, settings, network |
| Audio & Playback | Audio engine, player queue, play state reporting, media session |
| Library | Albums, artists, songs, genres, playlists, folders |
| Offline | Downloads management, download file handling, concurrent chunks |
| Connectivity | WebSocket, P2P connections, remote control sessions, remote playback |
| UI & UX | Modals, alerts, toasts, themes, selection, scroll, haptics, loading |
| Player Experience | Player UI state, visualizer, lyrics, DJ mode |
| Advanced | Sonic analysis, device metrics, logging, scheduler, notifications |
Each feature slice manages its own data independently but can react to changes in other slices.
4. Cross-Feature Communication
Features communicate by sending messages to each other through the central state tree. When one feature completes an important task, it can trigger actions in other features:
Example: Playing a Track
- You tap "Play" on a song in the library
- Library feature dispatches "Play Track" action
- Player feature receives action, loads the track
- Player dispatches "Start Audio" to the Audio Engine feature
- Audio Engine reports "Playback Started"
- Player UI feature updates to show the full-screen player
- Media Session feature updates the OS-level controls (lock screen, car UI)
All of this happens automatically through the message system, without any feature directly calling another feature's code.
5. Composed Data Views
The app builds unified views that combine data from multiple feature slices:
Example: "Is Playing" Status
- If playing locally: reads from the Audio Engine
- If playing on a remote device: reads from Remote Playback
- The UI sees one consistent "Is Playing" value regardless of where playback is happening
This pattern ensures the user interface stays simple even when the underlying system is complex.
6. Startup Sequence
When Llamafin launches, it follows a carefully orchestrated startup sequence:
1. Initialise storage layer
↓
2. Load user settings
↓
3. Load persisted downloads
↓
4. Initialise network monitoring
↓
5. Rehydrate authentication (verify session)
↓
6. Load library data
↓
7. Verify server connection
↓
8. Mark app as "Ready"
Progress Tracking: The app tracks 11 critical startup tasks and displays a progress indicator (0-100%) with localized messages like "Loading your library..." or "Preparing playback engine...".
Error Recovery: If startup stalls or fails:
- The app notifies you and offers to retry
- You can choose to continue anyway (useful in offline scenarios)
- Unrecoverable errors trigger automatic cache clearing and restart
7. Session Persistence
Your session is automatically restored when you reopen the app:
- Authentication tokens are securely stored and revalidated
- Your previous queue can be restored (if enabled)
- Settings are loaded from persistent storage
- Downloads are synchronised with the persisted state
8. Manual State Persistence
Unlike apps that automatically save everything, Llamafin uses a selective persistence strategy:
- Critical data (auth tokens, settings, downloads) is explicitly saved
- Ephemeral data (current scroll position, temporary filters) is not persisted
- Version-aware: The app tracks its version and clears outdated caches when updated
This approach ensures fast startup times while keeping important data safe.
9. Selective Debugging
For development purposes, the app includes a targeted logging system that monitors only the most critical operations:
- Track changes (play, pause, next, previous)
- Audio engine state changes (started, paused, stopped)
- Settings changes that affect playback
This focused approach avoids overwhelming developers with log data while providing visibility into the core playback pipeline.
Configuration
State management operates largely behind the scenes, but some user-facing features are influenced by state persistence settings:
Downloads Persistence
| Setting | Description |
|---|---|
| Persist Downloads | Download state is saved and restored across app sessions |
| Download Quality | Affects which tracks are available offline |
| Smart Downloads | Automatic downloads based on your listening habits |
Playback State
| Setting | Description |
|---|---|
| Resume Queue | Whether to restore your previous queue on app restart |
| Resume Position | Whether to seek to where you left off in each track |
Settings Persistence
All settings are automatically persisted and restored:
- Appearance (theme, fonts, visualizers)
- Playback (EQ, audio effects, loudness leveling)
- Music quality (streaming quality per network type)
- Downloads (quality, smart download rules, cache settings)
How It Works
State Update Process
1. User interacts with the app (tap, scroll, type)
↓
2. Component creates an action describing the event
↓
3. Action dispatched to the central store
↓
4. Reducer (pure function) processes the action and produces new state
↓
5. Store emits the new state
↓
6. All subscribed components automatically update
↓
7. Effects may trigger follow-up actions (API calls, navigation, other features)
Feature Communication Process
1. Feature A completes an important operation
↓
2. Feature A's effect dispatches an action for Feature B
↓
3. Feature B's reducer processes the action
↓
4. Feature B's state updates
↓
5. Feature B's effects may trigger additional follow-up
Key Principle: Features never call each other's code directly. They communicate purely through actions on the shared state tree.
Startup Sequence Details
1. App Load action dispatched
↓
2. Storage initialised
↓
3. Settings loaded from storage
↓
4. Download state loaded
↓
5. Network listeners started
↓
6. Auth session rehydrated
↓
7. Library data loaded
↓
8. Progress calculated (tasks completed / total tasks)
↓
9. App Ready action dispatched
↓
10. UI transitions from loading screen to main interface
Error Recovery Process
1. Startup task takes too long (timeout)
↓
2. App Stalled state activated
↓
3. Loading screen shows "Still working on it..." message
↓
4. User options:
a. Wait longer: App returns to initialising state
b. Continue anyway: App transitions to ready state
↓
5. If task fails completely:
a. App Load Failure state activated
b. Error message displayed with retry option
Sign-Out Process
When you sign out, the app orchestrates a clean shutdown:
1. App Sign Out action dispatched
↓
2. Authentication state cleared
↓
3. Settings cleared (server-specific settings removed)
↓
4. Player queue cleared
↓
5. App returns to login screen
Integration with Other Features
| Feature | State Integration | Description |
|---|---|---|
| Authentication | Session rehydration, user ID tracking | Auth state gates access to all other features |
| Player & Queue | Queue state, playback status, shuffle/repeat | Player state composed with audio engine and remote playback |
| Audio Engine | Playback state, EQ settings, audio effects | Audio state drives player UI and media session |
| Downloads | Persisted download state, download progress | Downloads state restored on app restart |
| Settings | All user preferences | Settings loaded during startup, drive UI behaviour |
| Network | Connection status, offline mode | Network state triggers alerts and affects API calls |
| Library | Albums, artists, songs, playlists, genres, folders | Library data loaded during startup, paginated browsing |
| UI Components | Modals, alerts, toasts, loading, theme | UI state reflects what overlays are visible |
| Connect P2P | Remote device connections | Connect state affects playback target routing |
| Remote Control | Session management, remote playback | Session state combined with local playback for unified view |
Error Handling & Reliability
Startup Reliability
- Timeout protection: If any startup task takes too long, the app notifies you
- Graceful degradation: You can choose to continue even if some tasks fail
- Automatic retry: Network-dependent tasks retry when connection is restored
- Cache wipe: Unrecoverable errors trigger automatic cache clearing and restart
State Consistency
- Pure reducers: State transformations are deterministic -- same input always produces same output
- No direct state mutation: Changes always produce new state objects, never modify existing ones
- Single source of truth: All features see the same data, eliminating inconsistencies
Persistence Reliability
- Selective saving: Only critical data is persisted (auth, settings, downloads)
- Version awareness: App detects version changes and clears outdated caches
- Manual rehydration: Each feature controls how and when it restores its own state
- Storage queuing: Storage calls are queued until the storage layer is ready
Cross-Feature Resilience
- Feature isolation: One feature's failure doesn't crash other features
- Action-based communication: Features react to actions, not direct calls
- Recovery actions: Network reconnection can trigger recovery actions in dependent features
Technical Specifications
| Specification | Value | Description |
|---|---|---|
| Feature Slices | 45+ | Independent state domains |
| Effects Classes | 60+ | Side-effect handlers across all features |
| Startup Tasks Tracked | 11 | Critical initialisation steps monitored |
| Logged Action Types | 10 | Selective logging for core playback pipeline |
| State Slices Logged | 2 | Player and Audio only (development mode) |
| Meta-Reducers | 2 | Debug utilities (development only) |
| State Persistence | Manual | Per-feature rehydration via effects |
| Storage Backend | Ionic Storage | Cross-platform persistent storage |
| Runtime Checks | Default | No extra serializability/immutability checks |
| Store DevTools | Disabled | Time-travel debugging not enabled |
| Bootstrap Method | Functional providers | Angular 16+ standalone component bootstrap |
Related Documentation
- Architecture Overview -- Overall application architecture
- Authentication & Security -- Session persistence and rehydration
- Settings System -- User preference persistence
- Offline Support -- Network state integration
- UI Components -- UI state slices