Jetpack DataStore 및 이를 구현하는 방법.

Jetpack DataStore는 SharedPreferences를 대체하는 것을 목표로 하는 Coroutines 및 Flow를 기반으로 하는 새롭게 개선된 라이브러리입니다. SharedPreferences에 비해 장점은 무엇입니까? 데이터는 비동기식으로 일관되고 트랜잭션 방식으로 저장됩니다.

주제를 조금 더 깊이 살펴보면;
  • DataStore는 데이터 저장 및 읽기를 위한 비동기 API(Flow를 통해)를 제공하지만 SharedPreferences는 데이터 읽기만을 위한 비동기 API를 제공합니다.
  • DataStore는 Dispatchers.IO를 사용하기 때문에 UI 스레드에서 호출하는 것이 안전하지만 SharedPreferences는 UI 스레드를 차단합니다. apply() 및 commit()에는 오류 신호 메커니즘이 없으며 apply()는 종종 ANR의 소스가 되는 fsync()의 UI 스레드를 차단합니다.
  • DataStore는 런타임 예외로부터 안전하지만 SharedPreferences는 런타임 예외에서 파싱 오류를 발생시킵니다.

  • 또한 DataStore에는 두 가지 다른 구현이 있습니다.
    환경 설정 DataStore 및 Proto DataStore

    Preferences DataStore – SharedPreferenecs와 같은 키-값 쌍을 통해 데이터를 저장하고 액세스하지만 이 구현은 유형 안전성을 제공하지 않습니다.

    Proto DataStore – 데이터를 사용자 지정 개체로 저장하고 프로토콜 버퍼를 사용하여 스키마를 정의해야 합니다. 또한 형식 안전성을 제공합니다.

    참고: 오늘 우리는 Preferences DataStore의 구현을 살펴보고 있습니다.



    먼저 몇 가지 종속성을 추가해야 합니다.

        // Preferences DataStore
        implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01" 
    
        // Lifecycle components
        implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
        implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
        implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 
    

    좋아요, 이름과 성이라는 사용자 정보를 저장한다고 가정해 봅시다. 그녀는 우리의 UI입니다.



    다음으로 생성자에서 컨텍스트를 사용하는 명명된 UserInfo 클래스를 만듭니다.

    class UserInfo(context: Context) { 
    

    아래에서 데이터 저장소를 만들고 이름을 지정해야 합니다.

    class UserInfo(context: Context) {
    
        val dataStore = context.createDataStore("user_info")
    
    }
    

    그런 다음 몇 가지 키를 만들어야 하므로 이를 위해 컴패니언 개체를 만듭니다.

    우리는 USER_NAME_KEY && USER_SURNAME_KEY라는 두 개의 키를 생성합니다. 이 키는 String 유형의 preferencesKey와 같을 것입니다. 그런 다음 실제 이름 USER_NAME && USER_SURNAME을 지정해야 합니다.

     class UserInfo(context: Context) {
    
        val dataStore = context.createDataStore("user_info")
    
        companion object {
            val USER_NAME_KEY = preferencesKey<String>("USER_NAME")
            val USER_SURNAME_KEY = preferencesKey<String>("USER_SURNAME")
        }
    } 
    

    다음으로 데이터를 저장하기 위해 정지 함수를 만들고 두 개의 인수를 사용합니다. 여기에서 dataStore.edit를 호출하여 값을 저장합니다.

      suspend fun saveUserInfo(name: String, surname: String ) {
            dataStore.edit {
                it[USER_NAME_KEY] = name
                it[USER_SURNAME_KEY] = surname
            }
        }
    

    따라서 할당한 키에 데이터를 저장하는 작업을 처리합니다.

    이제 데이터를 검색하는 데 도움이 되는 두 가지 흐름을 만듭니다. 그리고 키에 매핑하고 null인지 확인하고 빈 문자열("")을 반환합니다.

    val userNameFlow: Flow<String> = dataStore.data.map {
            it[USER_NAME_KEY] ?: ""
        }
    
    val userSurnameFlow: Flow<String> = dataStore.data.map {
            it[USER_SURNAME_KEY] ?: ""
        } 
    

    좋아요 지금까지 UserInfo로 끝났습니다. 이제 MainActivity로 이동합니다.

    먼저 UserInfo에 대한 lateinit var와 이름 및 성에 대한 2개의 변수를 만듭니다.

     lateinit var userInfo: UserInfo
    
     var name = ""
     var surname = ""
    

    그리고 onCreate 메서드를 사용하여 userInfo를 인스턴스화하고 데이터를 검색하고 저장하는 두 가지 메서드를 작성할 것입니다.

     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            userInfo = UserInfo(this)
    
            saveData()
    
            observeData()
        }
    

    이 다음에는 saveData()
    여기 안에는 아시다시피 UserInfo 클래스에 정지 함수를 만들었습니다. 따라서 이것은 CoroutineScope에서 수행되어야 합니다. GlobalScope.launch를 호출하고 이 블록 코드 내에서 saveUserInfo() 메서드를 호출합니다.

     private fun saveData() {
            buttonSave.setOnClickListener {
                name = etName.text.toString()
                surname = etSurname.text.toString()
    
                GlobalScope.launch {
                    userInfo.saveUserInfo(name, surname)
                }
            }
        }
    

    데이터를 저장한 후 LiveData를 관찰하고 검색하여 TextView에 쓸 것입니다.

    private fun observeData() {
            userInfo.userNameFlow.asLiveData().observe(this, {
                name = it
                tvName.text = "Saved name: $it"
            })
    
            userInfo.userSurnameFlow.asLiveData().observe(this, {
                surname = it
                tvSurname.text = "Saved surname: $it"
            })
        }
    




  • 또한 앞에서 언급했듯이 DataStore는 파일에서 데이터를 읽는 런타임 예외로부터 안전합니다. 데이터를 읽는 동안 오류가 발생하면 IOExceptions가 발생하므로 map() 전에 이러한 예외를 처리할 수 있습니다.

  • val userNameFlow: Flow<String> = dataStore.data
            .catch { exception ->
                if (exception is IOException) {
                    Log.d("DataStore", exception.message.toString())
                    emit(emptyPreferences())
                } else {
                    throw exception
                }
            }.map {
                it[USER_NAME_KEY] ?: ""
            }
    
    

    그래, 그게 다야.

    좋은 웹페이지 즐겨찾기