DataStore는 SharedPreferences를 비동기로 사용하기 위한 라이브러리입니다. DataStore는 프로토콜 버터(구조화된 데이터를 직렬화 하는 메커니즘)를 사용하여 Key-value타입 혹은 유형이지정된 객체를 로컬에 저장할 수 있는 기능입니다.
DataStore는 Corouine + Flow를 사용할 수 있어 비동기적이고 일관적인 트랜잭션 방식으로 데이터를 저장합니다.
DataStore는 key-value 타입으로 구성되어 있는 Preferences DataStore랑 사용자가 정의한 데이터를 저장할 수 있는 Proto DataStore 이렇게 두 종류가 존재합니다. Proto DataStore를 사용하게 된다면 프로토콜 버퍼를 이용하여 스키마를 정의해야합니다. 이는 데이터 타입을 보장해줄 뿐더러 SharedPreferences 보다 빠르고 단순합니다.
간단하게 특징을 요약한다면
- DataStore는 코투린과 Flow를 통해 읽고 쓰기에 대한 비동기 API를 제공합니다.
- DataStore는 UI 스레드를 호출해도 안전합니다.(Dispather.IO 아래에서 작동을 하기 때문입니다.)
- runtime exception으로부터 안전합니다.
class DataStoreModule(private val context : Context) {
private val Context.dataStore by
preferencesDataStore(name = "dataStore")
private val stringKey = stringPreferencesKey("key_name")
private val intkey = intPreferencesKey("key_name")
...
}
먼저 globalDataStore속성을 통하여 DataStore를 생성합니다.
이렇게 DataStore를 생성하고 난 뒤에는 저장할 값의 키들을 생성해줍니다. 키값의 경우 'type'PreferencesKey("name")으로 생성을 해주면 됩니다.
DataStore에서는 데이터를 읽을 떄 데이터는 Flow 객체로 전달이 되기에 다음과 같이 사용합니다.
val text : Flow<String> = context.dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map {preferences ->
preferences[stringKey] ?: ""
}
map()을 이용해서 DataStore에 저장되어 있는 값을 미리 지정해둔 키를 통해 가져옵니다. 이때 반환되는 값은 Flow 타입입니다. 또한 작업에 실패하였을 때를 대비에 catch()문으로 exception을 조건을 해줍니다.
DataStore의 값을 쓸때는 edit()를 이용합니다. DataStore에 값을 작성하기 위해서는 반드시 비동기로 동작해야하기에 suspend를 이용합니다.
suspend fun setText(text : String){
context.dataStore.edit { preferences ->
preferences[stringKey] = text
}
}
class SampleApplication : Application() {
private lateinit var dataStore : DataStoreModule
companion object {
private lateinit var sampleApplication: SampleApplication
fun getInstance() : SampleApplication = sampleApplication
}
override fun onCreate() {
super.onCreate()
sampleApplication = this
dataStore = DataStoreModule(this)
}
fun getDataStore() : DataStoreModule = dataStore
}
DataStore는 Singleton으로 관리되어야하기에 Application에서 생성하고 사용합니다.
읽기의 경우 선언해 놓은 변수에 접근한 후 Flow객체를 반환 받고 collect()를 이용하여 값을 읽어옵니다.
SampleApplication.getInstance().getDataStore().text.collect {
it -> //... do it Something
DataStore는 Flow형태로 값을 받아오기 때문에 값 변경이 있어야만 동작을 하게됩니다. 동작의 변경이 아닌 원하는 타이밍에 값을 가져오고 싶다면 first()함수를 이용해 값을 호출할 수 있습니다.
val text = SampleApplication.getInstance().getDataStore().
text.first()
DataStore에 값을 저장하고 싶을때는 DataClass에 미리 작성해 놓은 함수를 호출하면됩니다. 비동기 작업이기에 코루틴이나 RxJava로 호출해주면 됩니다.
CoroutineScope(Dispatchers.Main).launch {
val text = "Simple"
SampleApplication.getInstance().getDataStore().
setText(text)
}
SharedPreferences를 대체하기 위해 등장한 만큼 많은 장점을 가지고 있습니다. 특히 Coroutine Flow를 자체적으로 지원해주다 보니 훨씬 비동기적이고 안전한 Read & Wrtie 동작을 할 수 있는 것이 가장 큰 장점이라고 할 수 있습니다.
https://developer.android.com/topic/libraries/architecture/datastore?hl=ko