-
Notifications
You must be signed in to change notification settings - Fork 12
5.3 Local Storage and Persistence
Relevant source files
The following files were used as context for generating this wiki page:
This document covers the local storage and persistence mechanisms used by AudioScape to maintain user data, preferences, and downloaded content across app sessions. The system employs multiple storage strategies including MMKV for high-performance key-value storage and the file system for library data and downloaded media files.
For information about state management patterns and Redux integration, see State Management. For details about external API data flows, see External API Integration.
AudioScape implements a multi-layered persistence strategy that separates different types of data based on their access patterns and requirements:
graph TB
subgraph "AudioScape Storage Systems"
subgraph "MMKV Storage"
MMKV["MMKV Instance"]
SETTINGS["App Settings"]
PREFS["User Preferences"]
end
subgraph "File System Storage"
LIBRARY_JSON["libraryData.json"]
DOWNLOADS_DIR["Downloads Directory"]
ARTWORK_DIR["Artwork Cache"]
end
subgraph "Redux State Management"
LIBRARY_STORE["LibraryState"]
FAVORITES["favoriteTracks[]"]
PLAYLISTS["playlists{}"]
DOWNLOADED["downloadedTracks[]"]
end
subgraph "Device File System"
DOCUMENTS["DocumentDirectory"]
MEDIA_LIB["MediaLibrary"]
end
end
MMKV --> SETTINGS
MMKV --> PREFS
LIBRARY_STORE --> LIBRARY_JSON
LIBRARY_JSON --> DOCUMENTS
DOWNLOADS_DIR --> MEDIA_LIB
ARTWORK_DIR --> DOCUMENTS
FAVORITES --> LIBRARY_JSON
PLAYLISTS --> LIBRARY_JSON
DOWNLOADED --> LIBRARY_JSON
Sources: storage.ts:1-16, store/library.tsx:1-407
The application uses MMKV for high-performance, synchronous key-value storage of preferences and settings that require frequent access.
The storage instance is initialized as a singleton and provides synchronous access to stored data:
graph LR
APP["Application"] --> STORAGE["storage"]
STORAGE --> MMKV_INSTANCE["MMKV()"]
MMKV_INSTANCE --> SET["set(key, value)"]
MMKV_INSTANCE --> GET["getString(key)"]
MMKV_INSTANCE --> DELETE["delete(key)"]
MMKV_INSTANCE --> CLEAR["clearAll()"]
The MMKV storage is encrypted by default and provides better performance than AsyncStorage for frequent read/write operations.
Sources: storage.ts:9-15
The core library data including favorites, playlists, and downloaded track metadata is persisted using expo-file-system as JSON files.
The LibraryState interface defines the complete structure of persisted library data:
| Field | Type | Description |
|---|---|---|
favoriteTracks |
Song[] |
Array of user's favorite songs |
playlists |
Record<string, Song[]> |
User-created playlists mapped by name |
downloadedTracks |
DownloadedSongMetadata[] |
Metadata for locally stored songs |
activeDownloads |
Record<string, {song, progress}> |
Currently downloading songs with progress |
Sources: store/library.tsx:16-21
graph TD
subgraph "Redux Actions"
TOGGLE_FAV["toggleFavorite"]
ADD_PLAYLIST["addToPlaylist"]
ADD_DOWNLOAD["addDownloadedTrack"]
end
subgraph "Persistence Layer"
SAVE_TO_FILE["saveToFile()"]
FILE_PATH["dataFilePath"]
JSON_STRINGIFY["JSON.stringify()"]
WRITE_FILE["FileSystem.writeAsStringAsync()"]
end
subgraph "File System"
LIBRARY_JSON["libraryData.json"]
DOCUMENT_DIR["DocumentDirectory"]
end
TOGGLE_FAV --> SAVE_TO_FILE
ADD_PLAYLIST --> SAVE_TO_FILE
ADD_DOWNLOAD --> SAVE_TO_FILE
SAVE_TO_FILE --> JSON_STRINGIFY
JSON_STRINGIFY --> WRITE_FILE
WRITE_FILE --> FILE_PATH
FILE_PATH --> LIBRARY_JSON
LIBRARY_JSON --> DOCUMENT_DIR
The saveToFile function at store/library.tsx:55-62 automatically persists state changes to ${FileSystem.documentDirectory}libraryData.json whenever Redux actions modify the library state.
Sources: store/library.tsx:48-77, store/library.tsx:88-99, store/library.tsx:105-117
graph TD
subgraph "App Initialization"
INIT["initializeLibrary()"]
ENSURE_FILE["ensureFileExists()"]
LOAD_DATA["loadStoredData()"]
end
subgraph "File Operations"
CHECK_EXISTS["FileSystem.getInfoAsync()"]
READ_FILE["FileSystem.readAsStringAsync()"]
PARSE_JSON["JSON.parse()"]
end
subgraph "Redux Dispatch"
SET_FAVORITES["setFavoriteTracks"]
SET_PLAYLISTS["setPlaylists"]
SET_DOWNLOADS["setDownloadedTracks"]
end
INIT --> ENSURE_FILE
INIT --> LOAD_DATA
ENSURE_FILE --> CHECK_EXISTS
LOAD_DATA --> READ_FILE
READ_FILE --> PARSE_JSON
PARSE_JSON --> SET_FAVORITES
PARSE_JSON --> SET_PLAYLISTS
PARSE_JSON --> SET_DOWNLOADS
The initialization process at store/library.tsx:401-404 ensures data persistence across app restarts and handles file creation for new installations.
Sources: store/library.tsx:68-77, store/library.tsx:380-395, store/library.tsx:401-404
Downloaded songs and their associated metadata are stored using a combination of the device's media library and file system storage.
The DownloadedSongMetadata interface defines the structure for tracking downloaded content:
| Field | Type | Description |
|---|---|---|
id |
string |
Unique song identifier |
title |
string |
Song title |
artist |
string |
Artist name |
duration |
number? |
Duration in seconds |
localTrackUri |
string |
Local content:// URI for playback |
mediaLibraryAssetId |
string |
MediaLibrary asset ID |
localArtworkUri |
string? |
Local artwork URI |
downloadDate |
string |
ISO date string |
Sources: store/library.tsx:26-35
graph TD
subgraph "Download Process"
DOWNLOAD_REQUEST["downloadAndSaveSong()"]
FETCH_AUDIO["Fetch Audio Stream"]
FETCH_ARTWORK["Fetch Artwork"]
end
subgraph "File System Storage"
MEDIA_LIBRARY["MediaLibrary.createAssetAsync()"]
ARTWORK_CACHE["Local Artwork Storage"]
CONTENT_URI["content:// URI"]
end
subgraph "Redux State Update"
ADD_DOWNLOADED["addDownloadedTrack"]
METADATA["DownloadedSongMetadata"]
LIBRARY_JSON["libraryData.json"]
end
subgraph "Playback Integration"
TRACK_PLAYER["TrackPlayer.add()"]
LOCAL_PLAYBACK["Local File Playback"]
end
DOWNLOAD_REQUEST --> FETCH_AUDIO
DOWNLOAD_REQUEST --> FETCH_ARTWORK
FETCH_AUDIO --> MEDIA_LIBRARY
FETCH_ARTWORK --> ARTWORK_CACHE
MEDIA_LIBRARY --> CONTENT_URI
CONTENT_URI --> METADATA
ARTWORK_CACHE --> METADATA
METADATA --> ADD_DOWNLOADED
ADD_DOWNLOADED --> LIBRARY_JSON
METADATA --> TRACK_PLAYER
TRACK_PLAYER --> LOCAL_PLAYBACK
The download system creates local content:// URIs that can be used directly by react-native-track-player for offline playback, as referenced in the MusicPlayerContext at components/MusicPlayerContext.tsx:637-644.
Sources: app/(modals)/menu.tsx:318-340, store/library.tsx:183-196, components/MusicPlayerContext.tsx:608-688
The persistence layer ensures data consistency between Redux state and file system storage through automatic synchronization on state changes.
graph LR
subgraph "UI Components"
FAVORITE_BTN["Favorite Button"]
PLAYLIST_ACTION["Playlist Action"]
DOWNLOAD_BTN["Download Button"]
end
subgraph "Redux Actions"
TOGGLE_FAV["toggleFavorite"]
ADD_TO_PLAYLIST["addToPlaylist"]
ADD_DOWNLOAD["addDownloadedTrack"]
end
subgraph "Auto Persistence"
SAVE_TO_FILE["saveToFile(state)"]
FILE_WRITE["FileSystem.writeAsStringAsync()"]
end
FAVORITE_BTN --> TOGGLE_FAV
PLAYLIST_ACTION --> ADD_TO_PLAYLIST
DOWNLOAD_BTN --> ADD_DOWNLOAD
TOGGLE_FAV --> SAVE_TO_FILE
ADD_TO_PLAYLIST --> SAVE_TO_FILE
ADD_DOWNLOAD --> SAVE_TO_FILE
SAVE_TO_FILE --> FILE_WRITE
Each Redux reducer automatically triggers saveToFile() to ensure immediate persistence of state changes, eliminating the need for manual save operations.
Sources: store/library.tsx:88-99, store/library.tsx:105-117, store/library.tsx:183-196
The library provides type-safe hooks for accessing persisted data:
| Hook | Purpose | Return Type |
|---|---|---|
useFavorites() |
Access favorite tracks | {favoriteTracks, toggleFavoriteTrack} |
usePlaylists() |
Manage playlists | {playlists, addTrackToPlaylist, ...} |
useDownloadedTracks() |
Access downloaded songs | DownloadedSongMetadata[] |
useIsSongDownloaded(id) |
Check download status | boolean |
useActiveDownloads() |
Track download progress | Song[] with progress |
These hooks automatically subscribe to Redux state changes and provide real-time updates when persisted data changes.
Sources: store/library.tsx:283-294, store/library.tsx:300-327, store/library.tsx:333-373