Skip to main content

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
StepDescriptionExample
1. User ActionYou tap a button, scroll, or typeTap "Play" on a song
2. Action DispatchedThe app broadcasts a description of what happenedPlay Track Requested
3. State UpdatedPure logic processes the action and produces new stateQueue updated, track loaded
4. UI RefreshedAll affected screens update automaticallyPlayer 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:

CategoryFeature Slices
CoreApp lifecycle, authentication, settings, network
Audio & PlaybackAudio engine, player queue, play state reporting, media session
LibraryAlbums, artists, songs, genres, playlists, folders
OfflineDownloads management, download file handling, concurrent chunks
ConnectivityWebSocket, P2P connections, remote control sessions, remote playback
UI & UXModals, alerts, toasts, themes, selection, scroll, haptics, loading
Player ExperiencePlayer UI state, visualizer, lyrics, DJ mode
AdvancedSonic 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

  1. You tap "Play" on a song in the library
  2. Library feature dispatches "Play Track" action
  3. Player feature receives action, loads the track
  4. Player dispatches "Start Audio" to the Audio Engine feature
  5. Audio Engine reports "Playback Started"
  6. Player UI feature updates to show the full-screen player
  7. 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

SettingDescription
Persist DownloadsDownload state is saved and restored across app sessions
Download QualityAffects which tracks are available offline
Smart DownloadsAutomatic downloads based on your listening habits

Playback State

SettingDescription
Resume QueueWhether to restore your previous queue on app restart
Resume PositionWhether 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

FeatureState IntegrationDescription
AuthenticationSession rehydration, user ID trackingAuth state gates access to all other features
Player & QueueQueue state, playback status, shuffle/repeatPlayer state composed with audio engine and remote playback
Audio EnginePlayback state, EQ settings, audio effectsAudio state drives player UI and media session
DownloadsPersisted download state, download progressDownloads state restored on app restart
SettingsAll user preferencesSettings loaded during startup, drive UI behaviour
NetworkConnection status, offline modeNetwork state triggers alerts and affects API calls
LibraryAlbums, artists, songs, playlists, genres, foldersLibrary data loaded during startup, paginated browsing
UI ComponentsModals, alerts, toasts, loading, themeUI state reflects what overlays are visible
Connect P2PRemote device connectionsConnect state affects playback target routing
Remote ControlSession management, remote playbackSession 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

SpecificationValueDescription
Feature Slices45+Independent state domains
Effects Classes60+Side-effect handlers across all features
Startup Tasks Tracked11Critical initialisation steps monitored
Logged Action Types10Selective logging for core playback pipeline
State Slices Logged2Player and Audio only (development mode)
Meta-Reducers2Debug utilities (development only)
State PersistenceManualPer-feature rehydration via effects
Storage BackendIonic StorageCross-platform persistent storage
Runtime ChecksDefaultNo extra serializability/immutability checks
Store DevToolsDisabledTime-travel debugging not enabled
Bootstrap MethodFunctional providersAngular 16+ standalone component bootstrap