지금까지 토큰이나 간단한 데이터를 저장할 때 SharedPreferences
를 이용해 저장했다.
그런데 이제 SharedPreferences
말고 DataStore
를 이용해 데이터를 저장하는 걸 권장하고 있어서 이에 대해 공부하고 남기는 글!
처음 써봐서 제대로 적용한 건지 헷갈리지만 차차 더 깊게 공부해 봐야겠다
Preferences Datastore
와 Proto Datastore
두가지 방법의 데이터 저장 기법을 제공키-값
형태로 데이터가 저장되며 값은 숫자, 문자열등의 기초 데이터build.gradle에 추가
dependencies {
implementation "androidx.datastore:datastore-preferences:1.0.0"
}
일단 나는 DataStore 클래스를 따로 만들었다.
class EdiyaDataStore(private val context : Context) {
private val Context.dataStore by preferencesDataStore(name = "dataStore")
private val STAMP_MAIN_FIRST = booleanPreferencesKey(PREF_STAMP_MAIN_FIRST)
}
사용할 타입PreferencesKey("keyName")
과 같은 형태로 선언stringPreferencesKey
, intPreferencesKey
사용edit()
를 사용suspend
키워드를 통해 Coroutine 영역에서 동작할 수 있도록 해야 함suspend fun setStampMainFirst(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[STAMP_MAIN_FIRST] = value
}
}
EdiyaApplication.appApplication.getDataStore().setStampMainFirst(true)
val stampMainFirst: Flow<Boolean> = context.dataStore.data
// catch()를 사용하여 데이터 읽는 과정에서 문제가 생겼을 때 예외처리
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
preferences[STAMP_MAIN_FIRST] ?: false
}
map()
을 활용하여 STAMP_MAIN_FIRST에 대응하는 Value를 Flow 형태로 가져옴catch()
를 사용하여 데이터 읽는 과정에서 문제가 생겼을 때 예외처리import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
class EdiyaDataStore(private val context : Context) {
private val Context.dataStore by preferencesDataStore(name = "dataStore")
private val STAMP_MAIN_FIRST = booleanPreferencesKey(PREF_STAMP_MAIN_FIRST) // 스탬프 메인 진입 여부
// 스탬프 진입 값 갱신
suspend fun setStampMainFirst(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[STAMP_MAIN_FIRST] = value
}
}
val stampMainFirst: Flow<Boolean> = context.dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
preferences[STAMP_MAIN_FIRST] ?: false
}
}
DataStore는 싱글톤으로 관리되어야 함 -> Application에서 생성
private lateinit var dataStore : EdiyaDataStore
fun getDataStore() : EdiyaDataStore = dataStore
// Flow형태로 값을 받아오기 때문에 값 변경이 있어야만 동작함
// 동작의 변경이 아닌 원하는 타이밍에 값을 가져오고 싶다면 first()함수를 이용해 값을 호출
val dataStore = EdiyaApplication.appApplication.getDataStore()
// 비동기 방식
lifecycleScope.launch {
// Flow형태로 값을 받아오기 때문에 값 변경이 있어야만 동작함
// 동작의 변경이 아닌 원하는 타이밍에 값을 가져오고 싶다면 first()함수를 이용해 값을 호출
if (dataStore.stampMainFirst.first() && userInfo.dueToExpireStampCnt > 0) {
dataStore.setStampMainFirst(false)
ExpireStampDialog(mActivity, userInfo.dueToExpireStampCnt) // 소멸예정 스탬프 바텀시트 -> 최초 진입(오늘 + 앱실행 + 스탬프 서브메인 처음 진입), 소멸예정 스탬프 있을 경우
}
}
// 동기 방식
// 다만, runBlocking 안에서 무거운 작업은 피해야 함 -> 블로킹 되는 시간이 길어짐에 따라 프레임 드랍으로 인한 화면 버벅임(16ms), ANR(5s)로 이어질 수 있음
runBlocking {
if (dataStore.stampMainFirst.first() && userInfo.dueToExpireStampCnt > 0) {
dataStore.setStampMainFirst(false)
ExpireStampDialog(mActivity, userInfo.dueToExpireStampCnt)
}
}
// flow를 liveData로 바꿔서 observe하고 싶은 경우
dataStore.stampMainFirst.asLiveData().observe(this) {
...
}