ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 안드로이드 DataStore 예제
    안드로이드 학습/Android 기술면접 대비 2025. 2. 28. 18:01

    DataStore 설명 : 링크

     

    DataStore에는 2가지 종류가 있다. 

    • 기본 타입을 저장하려면 Preference Datastore :
    • 기본 타입 이외의 타입을 저장하려면 Proto Datastore를 사용해야 한다.

    Preferences DataStore 예제 

    1. build.gradle (Modulo:app)

    implementation "androidx.datastore:datastore-preferences:1.1.3"
    implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.3" // optional - RxJava2 support
    implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.3" // optional - RxJava3 support

     

    optional 부분은 Flow를 RxJava2로 변환할 필요 없이 바로 사용할 수 있게 지원한다. 

     

     

    2. Datastore 인스턴스를 만들기 위해 preferencesDataStore 위임을 사용하며 수신기로 Context를 사용 

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

     

     

    3. 저장하려는 데이터 타입과 key

    private val stringKey = stringPreferencesKey("key_name") // string

     

    데이터 타입에 따른  키 세팅

    더보기

    private val stringKey = stringPreferencesKey("string_key")   // String 저장 

    private val intKey = intPreferencesKey("int_key")  // Int 저장

    private val booleanKey = booleanPreferencesKey("boolean_key") // Boolean 저장

    private val floatKey = floatPreferencesKey("float_key")     // Float 저장

    private val doubleKey = doublePreferencesKey("double_key")  // Double 저장

    private val longKey = longPreferencesKey("long_key")     // Long 저장

     

    4. 데이터 저장

    // Preference에 Setting 하는 방법
    suspend fun setText(text: String) {
        context.dataStore.edit { preferences ->
            preferences[stringKey] = text
        }
    }
    • suspend를 사용해 코루틴 비동기 방식으로 데이터 저장이 된다.
    • preferences[stringKey] 부분에서 stringKey는 앞서 3번에서 만든 key를 넣엇 데이터를 저장한다. 

    5. 데이터 호출

    val testString: Flow<String> = context.dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }
        .map { preferences ->
            preferences[stringKey] ?: ""
        }
    
    • 데이터 호출 과정에서 예외처리가 가능하다. (catch{})
    • map을 통해 데이터 가공할 수 있고 최종적으로 Flow<> 형식으로 반환해준다. 

    6. Application Class

    class AioApplication : Application() {
    
        private lateinit var dataStore: DataStoreUtil
    
        companion object {
            private lateinit var instance: AioApplication
    
            fun getInstance(): AioApplication = instance
            fun getAppContext(): Context = instance.applicationContext
        }
    
        override fun onCreate() {
            super.onCreate()
            instance = this
            dataStore = DataStoreUtil(this)
        }
    
        fun getDataStore() : DataStoreUtil = dataStore
    }

     

    싱글톤 방식으로 앱 전체에서 사용할 수 있게 만들기 위해서 Application class에 선언해주고 호출도 해줄 수 있게 만들었다. 

     

    7. ViewModel 부분

    private val dataStoreUtil = AioApplication.getInstance().getDataStore()
    
    val dataStoreTestFlow: StateFlow<String> = dataStoreUtil.testString
        .stateIn(
            viewModelScope,
            SharingStarted.Lazily,
            ""
        )
    
    fun saveText(text: String) {
        viewModelScope.launch {
            dataStoreUtil.setText(text)
        }
    }
    

     

    8. Fragment

    viewLifecycleOwner.lifecycleScope.launch {
        viewModel.dataStoreTestFlow.collectLatest { text ->
            binding.tvDatastoreShowText.text = text
        }
    }

     

     

    반응형

     

    Proto Datastore 예제 :

    Proto Datastore는Preferences DataStore에 비해 추가적인 과정이 필요하다 

     

    1. build.gradle (Modulo:app)

    plugins {
        ... 생략 ...
        id "com.google.protobuf"
    }
    dependencies {
        implementation  "androidx.datastore:datastore-core:1.0.0"
        implementation  "com.google.protobuf:protobuf-javalite:3.18.0"
        ...
    }
    
    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:3.14.0"
        }
    
        // Generates the java Protobuf-lite code for the Protobufs in this project. See
        // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
        // for more information.
        generateProtoTasks {
            all().each { task ->
                task.builtins {
                    java {
                        option 'lite'
                    }
                }
            }
        }
    }

     

    2. 파일이름.proto 생성

     

    app/src/main/proto 디렉터리에 user_prefs.proto라는 새 파일을 만듭니다.

     

    syntax = "proto3";
    
    option java_package = "com.aio.kotlin.data.datastore";
    option java_multiple_files = true;
    
    message UserPreferences {
        // filter for showing / hiding completed tasks
        bool show_completed = 1;
    
        string username = 2;
    }

     

    option java_package = "com.aio.kotlin.data.datastore";
    • proto 파일을 컴파일하면 해당 패키지(com.aio.kotlin.data.datastore) 아래에 자동으로 클래스를 생성하게 된다.
     bool show_completed = 1;
    • 여기서 1은 필드 번호 (Field Number) 를 의미 하고 데이터를 송수신할 때 필드 번호 기반으로 매핑됨.

     

    3.serializer 만들기

    object UserPreferencesSerializer : Serializer<UserPreferences> {
        override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
        override suspend fun readFrom(input: InputStream): UserPreferences {
            try {
                return UserPreferences.parseFrom(input)
            } catch (exception: InvalidProtocolBufferException) {
                throw CorruptionException("Cannot read proto.", exception)
            }
        }
    
        override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
    }
    

     

    위에 설정한 위치에 serializer 파일을 만들었다.

    option java_package = "com.aio.kotlin.data.datastore";

     

    4. DataStore

    // Proto DataStore
    private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
        fileName = "user_prefs.pb",
        serializer = UserPreferencesSerializer
    )

     

    5. 데이터 저장

    suspend fun updateShowCompleted(completed: Boolean, data: String) {
        context.userPreferencesStore.updateData { preferences ->
            preferences
                .toBuilder()
                .setShowCompleted(completed)
                .setUsername(data)
                .build()
        }
    }

     

    6. 데이터 호출

    val userPreferencesFlow: Flow<ProtoDataStore> = context.userPreferencesStore.data
        .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data
            if (exception is IOException) {
                emit(UserPreferences.getDefaultInstance())
            } else {
                throw exception
            }
        }
        .map { preferences ->
            // Get our show completed value, defaulting to false if not set:
            val showCompleted = preferences.showCompleted
            val name = preferences.username
            ProtoDataStore(showCompleted, name)
        }

     

    7. ViewModel

    class DataStoreViewModel : ViewModel() {
    
        private val dataStoreUtil = AioApplication.getInstance().getDataStore()
    
        val newDataTestFlow: StateFlow<ProtoDataStore> = dataStoreUtil.userPreferencesFlow
            .stateIn(
                viewModelScope,
                SharingStarted.Lazily,
                ProtoDataStore(false, "")
            )
    
        fun saveProtoDataStore(data: String) {
            viewModelScope.launch {
                dataStoreUtil.updateShowCompleted(true, data)
            }
        }
    
    }

     

    8. Fragment

    viewLifecycleOwner.lifecycleScope.launch {
        viewModel.newDataTestFlow.collectLatest { text ->
            binding.tvDatastoreShowText.text = text.name
        }
    }
    

     

     

     

    종합적으로 봤을때 DataStoreUtil은 아래와 같다.

    // Preferences DataStore
    val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
    
    // Proto DataStore
    private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
        fileName = "user_prefs.pb",
        serializer = UserPreferencesSerializer
    )
    
    class DataStoreUtil(private val context: Context) {
    
        private val stringKey = stringPreferencesKey("key_name") // string
    
        // Preferences DataStore 데이터 얻기
        val testString: Flow<String> = context.dataStore.data
            .catch { exception ->
                if (exception is IOException) {
                    emit(emptyPreferences())
                } else {
                    throw exception
                }
            }
            .map { preferences ->
                preferences[stringKey] ?: ""
            }
    
        // Preferences DataStore 데이터 세팅
        suspend fun setText(text: String) {
            context.dataStore.edit { preferences ->
                preferences[stringKey] = text
            }
        }
    
        // Proto Datastore 데이터 얻기
        val userPreferencesFlow: Flow<ProtoDataStore> = context.userPreferencesStore.data
            .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data
                if (exception is IOException) {
                    emit(UserPreferences.getDefaultInstance())
                } else {
                    throw exception
                }
            }
            .map { preferences ->
                // Get our show completed value, defaulting to false if not set:
                val showCompleted = preferences.showCompleted
                val name = preferences.username
                ProtoDataStore(showCompleted, name)
            }
    
        // Proto Datastore 데이터 세팅
        suspend fun updateShowCompleted(completed: Boolean, data: String) {
            context.userPreferencesStore.updateData { preferences ->
                preferences
                    .toBuilder()
                    .setShowCompleted(completed)
                    .setUsername(data)
                    .build()
            }
        }
    }
Designed by Tistory.