코루틴 · 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 를 쓴다
- (위젯·포그라운드 서비스를 별도 프로세스로 돌리는 앱에서 실제로 마주친다)