안드로이드 시스템·Compose 용어 사전
데이터Preferences DataStore · SharedPreferences

DataStore

코루틴·Flow 기반의 비동기 설정 저장소. SharedPreferences를 대체한다.

코루틴 · Flow 기반의 비동기 설정 저장소.

SharedPreferences의 무엇이 문제인가

  • getString() 이 동기다 — 메인 스레드에서 디스크를 읽는다
  • 첫 로드가 동기라 앱 시작을 늦춘다
  • commit() 은 대놓고 블로킹, apply() 도 onPause/onStop 에서 대기한다
  • 오류를 알 방법이 없다 (디스크가 꽉 차도 조용히 실패)
  • 타입 안전하지 않다 (키가 문자열)
  • 변경 관찰이 리스너 기반이라 누수가 쉽다
val Context.dataStore by preferencesDataStore(name = "settings")

object Keys {
    val DARK_MODE = booleanPreferencesKey("dark_mode")
    val FONT_SCALE = floatPreferencesKey("font_scale")
}

class SettingsRepository @Inject constructor(private val store: DataStore<Preferences>) {
    val darkMode: Flow<Boolean> = store.data
        .catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
        .map { it[Keys.DARK_MODE] ?: false }

    suspend fun setDarkMode(on: Boolean) {
        store.edit { it[Keys.DARK_MODE] = on }
    }
}

catch로 IOException을 반드시 다룬다

파일이 깨졌거나 읽기에 실패하면 Flow 가 예외로 끝난다

  • 화면이 영영 갱신되지 않는다

catch 로 기본값을 흘려보내는 것이 표준 형태다

원자적 갱신

// edit 블록 전체가 트랜잭션이다
store.edit { prefs ->
    prefs[Keys.COUNT] = (prefs[Keys.COUNT] ?: 0) + 1
}

SharedPreferences 의 읽고-고치고-쓰기는 경합이 있었다 DataStore 는 edit 이 직렬화돼 안전하다

이전

preferencesDataStore(
    name = "settings",
    produceMigrations = { ctx -> listOf(SharedPreferencesMigration(ctx, "old_prefs")) }
)

첫 접근 시 자동으로 옮기고 원본을 지운다 — 사용자가 눈치채지 못한다

DataStore vs Room

  • DataStore — 키-값 몇십 개. 설정 · 토글 · 마지막 동기화 시각

    • 쿼리·정렬·조인이 없다. 파일 전체를 읽고 쓴다
  • Room — 구조화된 다건 데이터. 쿼리 · 인덱스 · 관계

  • "목록" 이 나오면 Room 이다

면접 함정

  • preferencesDataStore를 여러 번 선언한다 → 같은 파일에 두 인스턴스가 생기면 예외가 난다. 최상위에 한 번만 선언한다.
  • "DataStore가 SharedPreferences보다 빠르다" → 비동기라 메인 스레드를 안 막을 뿐, 처리량이 빠른 것은 아니다.

여러 값을 한 번에 읽는다

data class Settings(val darkMode: Boolean, val fontScale: Float)

val settings: Flow<Settings> = store.data.map {
    Settings(
        darkMode = it[Keys.DARK_MODE] ?: false,
        fontScale = it[Keys.FONT_SCALE] ?: 1f
    )
}.distinctUntilChanged()

키마다 Flow 를 노출하면 화면이 여러 개를 구독해 조합해야 한다 하나의 도메인 객체로 묶어 노출하는 편이 화면이 단순해진다

첫 값을 기다려야 할 때

// 앱 시작 시 테마를 정하려면 첫 값이 필요하다
val initial = runBlocking { store.data.first() }   // 시작 시간이 늘어난다

// 더 나은 방법 — 스플래시를 유지하며 기다린다
installSplashScreen().setKeepOnScreenCondition { viewModel.theme.value == null }

DataStore 는 비동기가 본질이라 '동기로 첫 값' 을 얻는 순간 장점이 사라진다 화면 깜빡임은 스플래시 유지나 기본값 우선 렌더로 푼다

프로세스가 여럿이면

DataStore 는 기본적으로 단일 프로세스 전용이다 같은 파일을 두 프로세스가 열면 예외가 나거나 데이터가 깨진다

멀티프로세스가 필요하면 MultiProcessDataStore 를 쓴다

  • (위젯·포그라운드 서비스를 별도 프로세스로 돌리는 앱에서 실제로 마주친다)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — DataStore — 설정·키값 저장(SharedPreferences 대체)