ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android DataStore 를 사용하여 파일에 간단한 정보 저장하기
    Java-Kotlin/Android 2026. 1. 14. 23:45
    반응형

    회사에서 제작하는 POS 프로그램의 정보(회사, 매장, 기기 번호)를 저장해 주어야 해서 Android에서 지원하는 두 개의 라이브러리를 비교해 보았다.

     

    1. SharedPreferences
      • 동기적으로 작동
      • Atomic Read/Write 보장 안됨
      • Key-Value 저장 지원
      • 파싱 에러 발생 시 RuntimeException Throw
      • 코드가 간단하여 러닝커브가 낮음
    2. DataStore(Jetpack)
      • 비동기적으로 작동
      • Atomic Read/Write 보장 됨
      • Key-Value, ProtoBuf(타입을 정의 및 직렬화 가능) 저장 지원
      • 오류시 IOException로 에러를 Throw
      • 코드가 SharedPreferences보다 복잡하여 러닝커브가 상대적으로 높음
      • Kotlin Coroutine, Flow을 사용하도록 설계됨

     

    고민해 본 결과 DataStore를 사용하여 개발하기로 했다.

     

    DataStore를 선택한 가장 큰 이유는 이렇다.

    • 프로그램 동작중 여러 쓰레드에서 Read/Write가 발생할 수 있음
    • Flow를 사용하여 에러 핸들링이 상대적으로 간편함(Flow에는 catch 블록이 있음)

     

    구현할 기능: 간단한 읽기(Read) 쓰기(Write) 정도만 구현하기로 했다.

     

    현재 프로젝트는 Kotlin Multiplatform에 Koin으로 di 하는 환경이기에 DataStore 인스턴스와 그것을 inject 받아서 생성되는 DataStoreRepository를 구현하여 읽기 쓰기 기능을 제작했다.

     

     

     

    Gradle

    # libs.versions.toml
    
    androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version = "1.2.0" }

     

     

    Koin DI

    # KoinModule.kt
    
    single { DataStoreProvider.getInstance().getDataStore() }
    single { DataStoreRepository(get()) }

     

     

    DataStoreProvider

    actual class DataStoreProvider private actual constructor() {
    
        private val dataStoreDirectory: File = Paths.get("${System.getProperty("user.home")}/POSPersistentResource/").toFile()
    
        private val dataStore = createDataStoreWithDefaults(
            emptyList()
        ) {
            dataStoreDirectory.resolve(dataStoreFileName).absolutePath
        }
    
        actual fun getDataStore(): DataStore<Preferences> {
            return dataStore
        }
    
        actual companion object {
            @Volatile
            private var instance: DataStoreProvider? = null
    
            actual fun getInstance(): DataStoreProvider {
                return instance ?: synchronized(this) {
                    instance ?: DataStoreProvider().also { instance = it }
                }
            }
        }
    }

     

     

    DataStoreRepository

    //데이터 스토어는 파일 저장 상수 느낌으로 사용
    //제너릭을 사용 할 경우 세이브에는 문제가 없지만 리드시 타입이 맞지 않을 경우 에러가 발생 할 수 있음
    class DataStoreRepository(private val dataStore: DataStore<Preferences>) {
        companion object {
            val TIMESTAMP_KEY = longPreferencesKey(name = "saved_timestamp")
            val COMPANY_CODE = stringPreferencesKey(name = "COMPANY_CODE")
            val STORE_CODE = stringPreferencesKey(name = "STORE_CODE1")
            val POS_NO = stringPreferencesKey(name = "POS_NO")
    
        }
    
        suspend fun <T> saveValue(key: Preferences.Key<T>, value: T): Boolean = try {
            dataStore.edit { preferences ->
                preferences[key] = value
            }
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    
        fun <T> readValue(key: Preferences.Key<T>, default: T): Flow<T> =
            dataStore.data
                .catch { exception ->
                    // 예외를 다시 던져서 호출 측에서 처리할 수 있도록 합니다.
                    throw exception
                }
                .map { preferences ->
                    preferences[key] ?: default // 키에 해당하는 값이 없으면 기본 값 반환
                }
    
        suspend fun <T> readValueOnce(key: Preferences.Key<T>, default: T): T {
            return readValue(key, default).first()
        }
    }

     

     

    읽기/쓰기 예시

    // File DataStore에 저장
    dataStoreRepository.saveValue(DataStoreRepository.COMPANY_CODE, baseSetting.companyCode)
    dataStoreRepository.saveValue(DataStoreRepository.STORE_CODE, baseSetting.storeCode)
    dataStoreRepository.saveValue(DataStoreRepository.POS_NO, baseSetting.posNo)
    
    // File DataStore에서 읽기
    val companyCode = dataStoreRepository.readValueOnce(DataStoreRepository.COMPANY_CODE, "")
    val storeCode = dataStoreRepository.readValueOnce(DataStoreRepository.STORE_CODE, "")
    val posNo = dataStoreRepository.readValueOnce(DataStoreRepository.POS_NO, "")

     

     

     

    댓글

Designed by Tistory.