데이터 저장소를 사용하여 유형화 대상 지속

aprevious post에서 우리는 첫 번째 데이터 저장소를 사용하여sharedReference를 이전하는 방법을 연구했다.이 글에서 우리는 원시 데이터 저장에 주목할 것이다.

🤔 무엇이 원시 데이터 저장을 남다르게 합니까?


기본 설정과 원본 데이터 저장소는 모두 데이터를 저장할 수 있지만, 그것들의 방식은 다르다.유형 보안은 Proto Data Store를 사용하는 경우에만 적용됩니다.
Proto 데이터 저장소의 경우 키를 사용할 필요가 없습니다.반대로, 우리는 프로토콜 버퍼 정의 모델을 사용합니다.프로토콜 버퍼를 통해 우리는 강력한 유형의 데이터를 영구화할 수 있다.
Shared Preferences와 Preferences 데이터 저장의 단점 중 하나는 정확한 형식의 접근 키를 사용할 수 없다는 것이다.Proto DataStore를 사용하면 스토리지의 종류를 알 수 있으며, 스토리지만 제공합니다.

🔍 프로토콜 버퍼란?


프로토콜 버퍼는 구글이 개발해 각종 구조화된 정보를 저장하고 교환하는 데 쓰인다.그것들은 구조화된 데이터를 서열화하는 데 쓰인다.그것은 XML과 다른 유사한 데이터 형식과 유사하지만, 더 작고, 더 빠르고, 더 간단하다.
서열화가 무엇인지 모르면 데이터 구조나 대상 상태를 저장 가능한 형식으로 바꾸는 과정입니다. 예를 들어 파일이나 메모리 데이터 버퍼입니다.

👷🏻 프로토콜 버퍼는 어떻게 작동합니까?


우리는 데이터의 구조화 방식을 정의해야 한다. 컴파일러는 우리가 구조화된 데이터를 쉽게 읽을 수 있도록 원본 코드를 생성할 것이다.
Proto Datastore를 통해 프로토콜 버퍼를 사용하여 사용자 정의 데이터 구조를 서열화하고 저장할 것입니다. 물론 필요할 때 값을 반서열화하고 읽을 것입니다.

🤿 우리 잠수하자.


이 간단한 문장project에서 우리는 앞의 문장과 같은'나를 기억해'기능을 실현했다.현재 Shared References를 사용하여 이 값을 저장하고 있으며, 선택한 후에 사용자를 환영 화면으로 다시 지정합니다.Proto 데이터 저장소를 사용하기 위해 코드를 마이그레이션합니다.
코드를 얻으려면 이 코드GitHub repo를 검토하십시오.
최종 코드는 proto_datastore 지점에 있습니다.

🏷️ Protobuf 모드 정의


Proto Datastore를 사용하기 위해서는 새로운 .proto 파일에서 Protobuf 모드를 정의해야 합니다.클래스에서 모드를 만드는 것이 아니라 모드에서 정의할 것입니다.이 모델을 작성할 때, 우리는 새로운 문법을 따라야 한다.
Android Studio에 Protocol Buffer Editor라는 플러그인을 설치합니다. 이 플러그인은 프로파일을 작성하는 데 도움을 줍니다.프로토 파일을 만들기 전에 설치해야 합니다.

설치 후 프로젝트 보기로 전환하고 app/src/main 아래에 프로토라는 새 디렉터리를 만듭니다.이 디렉토리에 새 파일user_prefs.proto이 만들어졌으며 다음과 같이 프로토콜 버퍼 모드를 정의했습니다.
syntax = "proto3";

option java_package= "com.yalematta.datastore_demo";
option java_multiple_files = true;

message UserPreferences {
  bool remember = 1;
  string username = 2;
  int32 luckyNumber = 3;
}
제가 이 문법을 소개하겠습니다.

1️⃣ 구문


Protobuff 문법은 두 가지 버전이 있는데 그것이 바로 proto2와 proto3이다.두 버전에 대한 자세한 내용은 documention를 참조하십시오.우리의 예에서, 우리는proto3을 사용할 것이다.

2️⃣ 옵션


그런 다음 두 가지 옵션을 작성합니다.
우선 우리java_package의 이름이다.컴파일러가 이 프로토콜 버퍼에서 클래스를 생성하는 곳을 알려 줄 필요가 있습니다.
두 번째 옵션은 java_multiple_files입니다.이것을true로 설정합니다. 이것은 이 원형 파일의 모든 최고급 메시지 대상에 단독 파일을 만들어야 한다는 것을 의미합니다.

셋️⃣ 메시지


메시지 키워드는 데이터 구조를 정의합니다.그 안에서 우리는 이 구조의 구성원을 정의했다.네가 이미 알아차렸듯이, 우리는 이 문법에서 서로 다른 기원 유형을 가지고 있다.

보기documentation를 통해 알 수 있듯이 자바의 int32는 정수를 나타내고 int64는 롱을 나타내며 bool은 Boolean을 나타낸다.
두 개의 구성원 필드를 포함하는 대상 User Preferences: bool,string, int32를 만들었습니다.1, 2, 3을 보았을 때 곤혹스러워하지 마라.이것은 실제 값이 아니라 메시지 2진 형식에서 식별 필드의 유일한 숫자이며, 메시지 대상이 사용되면 변경하지 말아야 한다.

🔌 의존 관계


계속하기 전에, 이 플러그인을 구축의 맨 위에 놓으십시오.그레델 서류.
plugins {
    id "com.google.protobuf" version "0.8.12"
}
그리고 두 가지 의존항을 추가해야 합니다. 하나는 Protobuf에 사용되고, 다른 하나는 Proto DataStore에 사용됩니다.
// proto dataStore
implementation  "androidx.datastore:datastore:1.0.0-beta01"
implementation  "com.google.protobuf:protobuf-javalite:3.10.0"
마지막으로 우리의 구축이 끝날 때.gradle 파일은 Protobuf를 설정하고 프로젝트를 동기화합니다.
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.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'
                }
            }
        }
    }
}
현재 우리는 이 플러그인을 추가했습니다. user_prefs.proto 파일에서 이 플러그인이 자동으로 생성한 파일을 볼 수 있어야 합니다.
자바 폴더의 파일을 보기 위해 프로젝트를 재구성합니다.
새 User Prefs 폴더를 찾았습니다. 이것은 우리의 원형 파일과 User Preferences 클래스를 대표합니다. 이것은 우리의 메시지 대상을 대표합니다.이 안에 자바 코드가 있습니다. 이 User Preferences 메시지 대상을 위해 Getter와setter를 실현했습니다.

↪️ 서열화 프로그램


프로토 파일에 정의된 데이터 형식을 어떻게 읽고 쓰는지 알려주기 위해서, 우리는 서열화 프로그램을 실현해야 한다.서열화 프로그램은 데이터를 저장하지 않았을 때 되돌아오는 기본값을 정의합니다.
프로젝트에서 User Preferences Serializer라는 클래스를 만들었습니다. 이 클래스는 Serializer를 확장합니다.우리는 readFrom과 writeTo 두 가지 방법을 실현했다.이러한 방법에서, 우리는 이 대상을 데이터 저장소에 어떻게 읽는지 정의했다.
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)
}

🗃️ 데이터 저장소


다음으로 User Preferences Repository라는 저장소를 만듭니다.
class UserPreferencesRepository(private val userPreferencesStore: DataStore<UserPreferences>){
    ...
}

📋 데이터 저장소에서 읽기


사용자Preferences Flow라는 새 변수를 만들었습니다. 형식은 Flow입니다.우리는 데이터를 저장해서 데이터를 읽고 이상을 포착하고, 이 경우 UserPreferences의 기본 실례를 보냅니다.
    val userPreferencesFlow: Flow<UserPreferences> = userPreferencesStore.data
        .catch { exception ->
            // dataStore.data throws an IOException when an error is encountered when reading data
            if (exception is IOException) {
                Log.e(TAG, "Error reading sort order preferences.", exception)
                emit(UserPreferences.getDefaultInstance())
            } else {
                throw exception
            }
        }

📝 데이터 저장소에 쓰기


suspend update Username 함수를 만듭니다. 이 함수는 UserPreferences 구성원 값에 따라 필드를 업데이트합니다.우리는 preference.toBuilder(). 을 호출하여 생성된 클래스에서 필요한setter 방법을 선택할 것입니다.
    suspend fun updateUsername(username: String) {
        userPreferencesStore.updateData { preferences ->
            preferences.toBuilder().setUsername(username).build()
        }
    }
주의: 모든 필드를 업데이트하는 방법을 만드는 것을 잊지 마세요.

🆑 데이터 저장소 지우기


데이터를 지우려면 함께 기본 설정을 지우거나 생성된 클래스에서 특정 기본 설정을 지울 수 있습니다.
    suspend fun clearDataStore() {
        userPreferencesStore.updateData { preferences ->
            preferences.toBuilder().clear().build()
        }
    }

    suspend fun clearUsername() {
        userPreferencesStore.updateData { preferences ->
            preferences.toBuilder().clearUsername().build()
        }
    }

🤙🏼 ViewModel에서 호출


LoginViewModel에서는 사용자 기본 설정에 대한 변수를 생성하여 데이터 저장소에서 데이터를 스트림으로 읽은 다음 LiveData로 변환합니다.
다음에 saveUserPreferences라는 새 함수를 만들고 업데이트할 값을 전달합니다.우리는viewModel 범위를 호출하고 협동 프로그램에서 다음 코드를 실행합니다. 왜냐하면 우리 저장소의 업데이트 함수는 Kotlin 협동 프로그램을 사용하기 때문입니다.
class LoginViewModel(private val userPreferencesRepository: UserPreferencesRepository) : ViewModel() {

    val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow.asLiveData()

    fun saveUserPreferences(remember: Boolean, username: String, luckyNumber: Int) {
        viewModelScope.launch(Dispatchers.IO) {
            userPreferencesRepository.updateRemember(remember)
            userPreferencesRepository.updateUsername(username)
            userPreferencesRepository.updateLuckyNumber(luckyNumber)
        }
    }

    fun clearUserPreferences() {
        viewModelScope.launch(Dispatchers.IO) {
            userPreferencesRepository.clearDataStore()
        }
    }
}

class LoginViewModelFactory(
    private val userPreferencesRepository: UserPreferencesRepository
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return LoginViewModel(userPreferencesRepository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
LoginViewModelFactory는 ViewModelProvider입니다.공장은 우리의 활동에서 우리의 LoginView 모델 실례를 만드는 것을 책임진다.LoginViewModel의 구조 함수에 필요한 데이터 저장소를 전달합니다.

🔬 활동 중에 그것을 관찰하다


Google 활동에서 사용자Preferences DataStore를 만들고 초기화하고 파일 이름과 서열화된 프로그램 클래스를 전달합니다.

🗄️ 데이터 저장소 만들기


private const val DATA_STORE_FILE_NAME = "user_prefs.pb"

val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = UserPreferencesSerializer
)

📦 SharedReference에서 마이그레이션


Shared References에서 기존 데이터를 이전하려면 데이터 저장소를 만들 때 Shared References 이름을 기반으로 이전을 추가해야 합니다.
데이터 저장소를 만들 때, 데이터 저장소 생성기를 업데이트하고migrations 매개 변수에 Shared References Migration의 실례를 포함하는 새 목록을 지정해야 합니다.
Shared References Migration에서Shared References에서 User Preferences까지의 매핑 논리를 정의합니다.
데이터 저장소는 Shared Reference에서 데이터 저장소로 자동으로 이동할 수 있습니다.
const val USER_PREFERENCES_NAME = "user_preferences"
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"

val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = UserPreferencesSerializer,
    produceMigrations = { context ->
        listOf(sharedPrefsMigration(context))
    }
)

fun sharedPrefsMigration(context: Context) = SharedPreferencesMigration(
    context, USER_PREFERENCES_NAME) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
    // Define the mapping from SharedPreferences to UserPreferences
    currentData
}
onCreate 함수에서 ViewModel을 초기화하고 필드의 값을 관찰합니다. 데이터가 변할 때마다 해당하는 텍스트 필드에서 업데이트합니다.
로그인 단추를 누르면 편집 텍스트와 checkBox 필드의 값을 저장하고saveUserPreferences 함수를 사용하여 데이터 저장소에 업데이트합니다.
class LoginActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLoginBinding
    private lateinit var viewModel: LoginViewModel

    private var rememberMe = false
    private var luckyNumber = 0
    private lateinit var username: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        viewModel = ViewModelProvider(
            this,
            LoginViewModelFactory(UserPreferencesRepository(userPreferencesStore))
        ).get(LoginViewModel::class.java)

        viewModel.userPreferencesFlow.observe(this, { userPreferences ->
            rememberMe = userPreferences.remember
            username = userPreferences.username
            luckyNumber = userPreferences.luckyNumber
            if (rememberMe) {
                startActivity(Intent(this, WelcomeActivity::class.java))
            }
        })

        binding.login.setOnClickListener {
            if (binding.remember.isChecked) {
                val name = binding.username.text.toString()
                var number = luckyNumber
                if (binding.luckyNumber.text.toString().isNotEmpty()) {
                    number = binding.luckyNumber.text.toString().toInt()
                }
                viewModel.saveUserPreferences(true, name, number)
            }
            startActivity(Intent(this, WelcomeActivity::class.java))
        }

        binding.remember.setOnCheckedChangeListener { compoundButton: CompoundButton, b: Boolean ->
            if (!compoundButton.isChecked) {
                viewModel.clearUserPreferences()
            }
        }

    }
}

💡 핵심 배달


이제 Preferences 데이터 저장소로 이동해 봅시다.
데이터 저장소:
  • Shared Reference의 대체품으로 대부분의 단점을 해결했다
  • Kotlin 프로토콜과 흐름을 사용하는 완전 비동기 API
  • 데이터 일관성 보장
  • 데이터 마이그레이션 처리
  • 데이터 손상 처리
  • 데이터 스토리지는
    Preferences DataStore :
  • 를 사용하여 데이터 저장 및 액세스
    원시 데이터 저장소:
  • 유형 보안 확보
  • 프로토콜 버퍼 사용 요구
  • 정의 구조

    ⏭ 다음


    만약 이 글이 당신에게 도움이 된다면, 혹은 저에게 안드로이드와 관련된 특정한 주제를 쓰게 하려면, 저에게 알려주세요.트위터에 DM을 보내주세요.✌🏼

    좋은 웹페이지 즐겨찾기