# How AccountManager Handles ElevenLabs API Key Storage and Lifecycle in Muse

> Discover how Muse's AccountManager securely stores and manages ElevenLabs API keys using Jetpack DataStore for reactive, atomic updates across your application.

- Repository: [Ko Shin/muse](https://github.com/kkoshin/muse)
- Tags: internals
- Published: 2026-03-05

---

**The AccountManager class persists the ElevenLabs API key using Jetpack DataStore as a reactive `Flow<String?>`, enabling atomic writes and automatic propagation to consumers like ElevenLabProcessor and the settings UI.**

The `AccountManager` class in the [kkoshin/muse](https://github.com/kkoshin/muse) repository centralizes ElevenLabs API key management for this Kotlin Multiplatform audio processing app. It orchestrates the complete lifecycle—from user input in the settings screen to reactive consumption by the ElevenLabs HTTP client—using AndroidX DataStore for type-safe, persistent storage.

## Persistent Storage with Jetpack DataStore

The manager receives a `DataStore<Preferences>` instance via constructor injection defined in **[`appModule.kt`](https://github.com/kkoshin/muse/blob/main/appModule.kt)** (available for both iOS and Android targets). It declares a typed preference key using `stringPreferencesKey`:

```kotlin
private val key = stringPreferencesKey("elevenLabsApiKey")

```

This key identifies the stored string value within the DataStore's `Preferences` container, ensuring type safety and avoiding string-typing errors elsewhere in the codebase.

## Reactive Read Access via Kotlin Flow

The current API key is exposed as a **cold `Flow<String?>`** property named `apiKey`. The implementation maps the DataStore data snapshot to the stored string value:

```kotlin
val apiKey: Flow<String?> = dataStore.data.map { preferences ->
    preferences[key]
}

```

Any component can collect this flow to receive instantaneous updates when the key changes. This reactive pattern eliminates manual synchronization between the storage layer and consumers such as **[`ElevenLabProcessor.kt`](https://github.com/kkoshin/muse/blob/main/ElevenLabProcessor.kt)** or the settings screen.

## Writing and Updating the API Key

The `setElevenLabsApiKey(apiKey: String)` method handles all writes with built-in validation and atomic persistence:

```kotlin
suspend fun setElevenLabsApiKey(apiKey: String) {
    check(apiKey.isNotBlank()) { "api key cannot be blank" }
    dataStore.edit { preferences ->
        preferences[key] = apiKey
    }
}

```

The `check` assertion ensures the input is non-blank before persisting. Because the write occurs within `DataStore.edit`, the change is committed to disk transactionally and automatically emitted to all collectors of the `apiKey` flow.

## API Key Lifecycle Management

The AccountManager handles four distinct phases of the API key lifecycle:

### Provisioning from the Settings UI

Users enter their ElevenLabs API key in **[`SettingScreen.kt`](https://github.com/kkoshin/muse/blob/main/SettingScreen.kt)**, which invokes `accountManager.setElevenLabsApiKey()` upon saving. This triggers the validation and persistence logic immediately.

### Persistent Storage Across Process Death

The key survives app restarts and process termination because DataStore writes to disk asynchronously. Upon app launch, the `apiKey` flow emits the previously stored value (or `null` if unset), allowing **[`ElevenLabProcessor.kt`](https://github.com/kkoshin/muse/blob/main/ElevenLabProcessor.kt)** to initialize the HTTP client with existing credentials.

### Reactive Observation by Consumers

Components like `ElevenLabProcessor` collect `accountManager.apiKey` to configure the ElevenLabs client dynamically. When a user updates the key in settings, the processor receives the new value via the flow and can reinitialize connections without requiring an app restart.

### Updates and Subscription Status

Calling `setElevenLabsApiKey` overwrites any previous value atomically. While no explicit "delete" method exists, the architecture also manages `subscriptionStatus` through a parallel pattern using a separate `statusKey`, allowing the app to react to changes in the user's ElevenLabs subscription tier alongside API key changes.

## Source Code Integration Points

| File | Role |
|------|------|
| **[`muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt)** | Core storage implementation with `apiKey` flow and `setElevenLabsApiKey` method. |
| **[`muse/src/iosMain/kotlin/io/github/kkoshin/muse/appModule.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/iosMain/kotlin/io/github/kkoshin/muse/appModule.kt)**<br>[`muse/src/androidMain/kotlin/io/github/kkoshin/muse/appModule.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/androidMain/kotlin/io/github/kkoshin/muse/appModule.kt) | Koin DI modules providing the `DataStore<Preferences>` singleton to AccountManager. |
| **[`muse/src/commonMain/kotlin/io/github/kkoshin/muse/feature/setting/SettingScreen.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/feature/setting/SettingScreen.kt)** | UI component capturing user input and triggering `setElevenLabsApiKey`. |
| **[`muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/ElevenLabProcessor.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/ElevenLabProcessor.kt)** | Consumes `accountManager.apiKey` to configure the ElevenLabs HTTP client reactively. |
| **[`muse/src/iosTest/kotlin/io/github/kkoshin/muse/core/manager/AccountManagerTest.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/iosTest/kotlin/io/github/kkoshin/muse/core/manager/AccountManagerTest.kt)** | Unit tests verifying key storage, retrieval, and update semantics. |

## Summary

- **AccountManager** uses Jetpack DataStore for type-safe, persistent storage of the ElevenLabs API key in [`muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt).
- The API key is exposed as a reactive `Flow<String?>` property named `apiKey`, enabling real-time updates across the app.
- Writes occur through `suspend fun setElevenLabsApiKey(apiKey: String)`, which validates non-blank input and commits atomically via `dataStore.edit`.
- The lifecycle spans provisioning in [`SettingScreen.kt`](https://github.com/kkoshin/muse/blob/main/SettingScreen.kt), persistent storage surviving process death, and reactive consumption by [`ElevenLabProcessor.kt`](https://github.com/kkoshin/muse/blob/main/ElevenLabProcessor.kt).
- Parallel storage for `subscriptionStatus` follows an identical pattern, supporting comprehensive ElevenLabs account management.

## Frequently Asked Questions

### How is the ElevenLabs API key stored in the Muse app?

The key is stored in AndroidX DataStore as a string preference identified by `stringPreferencesKey("elevenLabsApiKey")`. The AccountManager class in [`muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt`](https://github.com/kkoshin/muse/blob/main/muse/src/commonMain/kotlin/io/github/kkoshin/muse/core/manager/AccountManager.kt) encapsulates all read and write operations, providing a type-safe API over the underlying `Preferences` storage.

### What happens when the API key is updated?

When `setElevenLabsApiKey()` is called, the method validates that the string is non-blank, then writes it atomically to DataStore via `dataStore.edit`. This triggers the `apiKey` flow to emit the new value immediately, causing all collectors—such as the one in [`ElevenLabProcessor.kt`](https://github.com/kkoshin/muse/blob/main/ElevenLabProcessor.kt)—to receive the update and reconfigure their ElevenLabs client instances without requiring an app restart.

### How does ElevenLabProcessor receive API key changes?

`ElevenLabProcessor` collects the `apiKey` flow from AccountManager within a coroutine scope. Because AccountManager exposes the key as a cold `Flow<String?>`, the processor receives emissions whenever the stored value changes, enabling dynamic reconfiguration of the HTTP client credentials as soon as the user updates their settings.

### Where is the AccountManager instantiated?

The AccountManager is instantiated through Koin dependency injection defined in [`appModule.kt`](https://github.com/kkoshin/muse/blob/main/appModule.kt) for both iOS and Android platforms. The module provides a singleton instance backed by a `DataStore<Preferences>`, ensuring that all consumers share the same storage state and reactive updates across the entire Kotlin Multiplatform codebase.