Android

DataStore

YOONJELLY 2023. 12. 6. 11:43

 

DataStore

- Android Jetpack 라이브러리 중 하나

- 프로토콜 버퍼를 사용하여 키-값 쌍 혹은 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션

- 코루틴 및 Flow를 사용하여 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장하는 것이 특징


SharedPreferences vs DataStore

출처 : https://proandroiddev.com/lets-explore-jetpack-datastore-in-android-621f3564b57

 

DataStore 특징

- 비동기적으로 작동 (코루틴과 Flow를 통해 읽고 쓰기에 대한 비동기 API 제공)

- UI 스레드를 호출해도 안전 (Dispathers.IO 밑에서 작동)

- Runtime Exceptions로부터 안전

- SharedPreferences 데이터와의 병합 기능

- 일관성이 보장되는 트랜잭션 API를 제공


DataStore 종류

1) Preferences DataStore : 키 - 값으로 구성

2) Proto DataStore : 맞춤 데이터 유형의 인스턴스로 데이터 저장. '프로토콜 버퍼'를 사용하여 스키마를 정의 (-> 데이터 타입 보장, SharedPreferences보다 빠르고 단순)

 

* 프로토콜 버퍼 : 구글의 데이터를 직렬화하기 위한 매커니즘


Preferences DataStore 사용 방법

 

1) 의존성 추가

 dependencies {
        implementation "androidx.datastore:datastore-preferences:1.0.0"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0"
}

 

 

2) DataStore 클래스 생성

class DataStoreModule(private val context: Context) {
	
    private val Context.datastore by preferencesDataStore(name = "dataStore")
    // String 키 값
    private val stringKey = stringPreferencesKey("key_name")
    // Int 키 값
    private val intKey = intPreferencesKey("key_name")
}

 

 

1. DataStore에서 값 읽기

    val text : Flow<String> = context.dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }
        .map {preferences ->
            preferences[stringKey] ?: ""
        }

 

 

2. DataStore에 값 쓰기

suspend fun setText(text: String) {
	context.dataStore.edit { preferences ->
    	preferences[stringKey] = text
    }
}

 

 

3. 전체 클래스 모습

class DataStoreModule(private val context : Context) {

    private val Context.dataStore  by preferencesDataStore(name = "dataStore")
    private val stringKey = stringPreferencesKey("textKey")

    val text : Flow<String> = context.dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }
        .map {preferences ->
            preferences[stringKey] ?: ""
        }
        
    suspend fun setText(text : String){
        context.dataStore.edit { preferences ->
            preferences[stringKey] = text
        }
    }
}

 

 

 

3) DataStore 사용

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
}

 

 

4) Activity OR Fragment에서 값 읽고 쓰기

 

1. 값 읽기

 

- 변경하는 값을 감지하여 반영하고 싶을 경우

SampleApplication.getInstance().getDataStore().text.collect { it ->
	...
}

 

- 특정 시점에 한 번만 값을 받아오고 싶을 경우

val text = SampleApplication.getInstance().getDataStore().text.first()

 

 

2. 값 쓰기

CoroutineScope(Dispatchers.Main).launch {
	val text = "Sample"
    SampleApplication.getInstance().getDataStore().setText(text)
}

 

작성해두었던 함수를 활용하여 작성한다. suspend 함수이므로, 코루틴이나 RxJava를 통해 비동기적으로 호출해야 한다.