nueijeel

[Android] Data Store로 데이터 저장하기 본문

Android/공부

[Android] Data Store로 데이터 저장하기

nueijeel 2023. 12. 2. 01:17

 

이전에는 안드로이드 앱이 종료되었다가 다시 실행되었을때 데이터를 유지시켜 사용하기 위해서 SharedPreferences를 사용했다.

 

Share Preference는 key - value 쌍의 구조로 데이터를 앱 내부에 xml 파일로 저장하기 때문에, 앱이 삭제되기 전까지 작성된 데이터를 유지할 수 있었다. 주로 앱 전역에서 사용해야하는 간단한 데이터(로그인 유저 데이터 등)를 저장하는 용도로 사용되어 왔다.

 

하지만 제한적인 데이터 형식으로 복잡한 데이터 구조를 다루기 어렵다는 점과 저장된 파일에 쉽게 접근이 가능해 보안에 취약하다는 점, 비동기 작업 처리 중 UI 스레드 차단으로 인해 ANR 발생 가능성이 높다는 점 등.. 많은 한계점이 존재했었다.

 

따라서 현재 안드로이드에서는 SharedPreferences를 보완하기 위해 나온 Data Store로 대체를 권장하고 있다.

 

 

 

DataStore란

 

DataStore는 Jetpack의 구성요소로 Coroutine과 Flow를 사용하여 비동기적이고 일관된 방식으로 데이터를 저장한다.

 

DataStore는 앞서 설명한 SharePreferences와 같이 key - value 구조로 데이터를 저장하는 Preferences DataStore 방식과 유형이 지정된 객체(사용자가 정의한 클래스의 인스턴스)를 프로토콜 버퍼를 통해 저장할 수 있는 Proto DataStore 방식으로 나뉜다.

 

shared preference와 data store 특징 비교 (출처 : https://android-developers.googleblog.com/2020/09/prefer-storing-data-with-jetpack.html)

 

 

이번 글에서는 Preferences DataStore의 사용 방법에 대해서만 소개하려고 한다.

 

사용자가 앱에 로그인을 완료하면 해당 정보를 저장해두고, 이후 앱이 종료되었다가 실행되어도 로그인을 유지하는데 사용하려고 한다.

 

 

1. dependency 추가

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

 

모듈 수준의 build.gradle 파일에 해당 dependency를 추가해준다.

 

 

2. Preferences DataStore 인스턴스 생성

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "userPreferences")

 

해당 코드로 preferences datastore 인스턴스를 생성해준다.

name 매개변수로 지정된 문자열은 해당 객체의 이름에 해당한다.

 

앱 전역에서 유저 정보에 자유롭게 접근할 수 있도록 최상단 수준 파일에 인스턴스를 생성해주었다.

 

 

3. 데이터 키 정의

private val PREF_KEY_EMAIL = stringPreferencesKey("email")
private val PREF_KEY_ACCESS_TOKEN = stringPreferencesKey("accessToken")
private val PREF_KEY_REFRESH_TOKEN = stringPreferencesKey("refreshToken")

 

2에서 생성한 인스턴스에 저장될 데이터들의 key를 정의해준다. 

key의 종류에는 string, boolean, int, double, float, long, stringset 7가지가 있어서 저장하려는 value에 맞게 key를 정의해주면 된다.

 

로그인 시 받은 유저 이메일과 두 토큰을 저장해두기 위해 3개의 키를 정의했다.

 

 

4. 데이터 저장

class UserPreferences @Inject constructor(@ApplicationContext private val context: Context) {
    ...
    suspend fun setEmail(email: String){
        context.dataStore.edit { preferences ->
            preferences[PREF_KEY_EMAIL] = email
        }
    }

    suspend fun setAccessToken(accessToken: String){
        context.dataStore.edit { preferences ->
            preferences[PREF_KEY_ACCESS_TOKEN] = accessToken
        }
    }

    suspend fun setRefreshToken(refreshToken: String){
        context.dataStore.edit { preferences ->
            preferences[PREF_KEY_REFRESH_TOKEN] = refreshToken
        }
    }
}

 

hilt를 통해 application context를 클래스 생성자로 주입받아 datastore에 접근했다. edit() 함수를 호출해 datastore 값을 저장(수정) 할 수 있다. 람다 함수의 인자인 preferences는 data store 객체로 mutable preferences 형이기 때문에 3에서 정의한 key 값을 이용해 value를 저장(수정) 한다.

 

 

5. 데이터 불러오기

class UserPreferences @Inject constructor(@ApplicationContext private val context: Context) {
    ...
    val email: Flow<String?> = context.dataStore.data.map { preferences ->
        preferences[PREF_KEY_EMAIL]
    }

    val accessToken: Flow<String?> = context.dataStore.data.map { preferences ->
        preferences[PREF_KEY_ACCESS_TOKEN]
    }

    val refreshToken: Flow<String?> = context.dataStore.data.map { preferences ->
        preferences[PREF_KEY_REFRESH_TOKEN]
    }
}

 

dataStore.data는 현재 저장된 모든 데이터를 Flow<T> 형식으로 반환한다. map 함수를 통해 전체 데이터 중 해당 키값에 대응되는 value를 받아오도록 했다.

 

 

6. 데이터 변경 감지

userPreferences.email.collect { email ->
    Log.d("datastore_collect", "email: $email")
}

 

collect 메서드는 flow를 비동기적으로 받아오고 처리하는 데 사용되고, flow가 새로운 값을 수신할 때 마다 이 코드 블록이 실행된다. 값이 제대로 받아와 졌는지 확인하기 위해 로그를 출력해보았다.

 

 

 


 

flow에 대한 내용을 조금이나마 알고있어서 Preferences DataStore를 수월하게 사용해볼 수 있었다.

다음에는 Proto DataStore에 대해 포스팅해보려고 한다.

 

 

 

📎참고

 

https://developer.android.com/topic/libraries/architecture/datastore?hl=ko#synchronous

 

728x90