Development record of developer who study hard everyday.

, , , , ,

안드로이드 ProtoDataStore 사용법 및 예제

 안드로이드 ProtoDataStore 사용법 및 예제

안드로이드 개발 블로그

지난 글에 이어서 DataStore에 대해 공부해보자.

지금 다니고있는 회사가 에이전시회사라서 옛날 프로젝트를 유지보수해야하는 일들이 생긴다.

그럴 때 보면 내가 처음 보는 라이브러리가 있는데, 그 중 하나가 objectBox다.

정확히 살펴보지는 않았지만 대략 검색해보면, 객체 자체를 앱에 저장하는 라이브러리 같았다.

이와 비슷한 구글 공식 라이브러리가 있다.

바로, Proto DataStore이다.


1. dependency 추가

app 수준의 build.gradle에 아래와 같이 종속성 추가를 해준다.

dependencies {
//prefer datastore
implementation "androidx.datastore:datastore-preferences:1.0.0"

//proto DataStore

implementation("androidx.datastore:datastore-core:1.0.0")
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
    //coroutine
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")

}

app 수준의 build.gradle 맨 아래에 아래처럼 설정을 해준다.

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'
}
}
}
}
}

app 수준의 build.gradle에 plugin을 추가해준다.

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id "com.google.protobuf" version "0.8.12" //추가
}


2. 스키마 정의

간단히 설명하자면, 내가 저장할 객체의 클래스를 정의해준다고 생각하면 쉽다.

이때, Protobuf(프로토콜 버퍼)를 사용하는데, 구글에서 만든 라이브러리라고 생각하면 된다.

다양한 언어로 객체의 타입을 정의하는데 장점이 많은 라이브러리다.

https://developers.google.com/protocol-buffers/docs/proto3?hl=ko

자세히 알고 싶은 사람들은 위 공식문서를 참고하자.


proto 파일 저장경로

먼저, 위 캡쳐화면처럼 app/src/main/proto 디렉터리에 .proto 파일을 만들어준다.

디렉토리 만드는 방법은 알거 같고 파일을 만들 때는 오른쪽 버튼 눌러서 New - File 을 누르면 된다.

이제, 스키마를 아래와 같이 정의한다.

syntax = "proto3";    //1

option java_package = "com.boo.sample.sampledatastore";    //2
option java_multiple_files = true;

message Person{    //3
string firstName = 1;
string lastName = 2;
int32 age = 3;
}

1=> proto3 문법을 따른다

2=> 패키지명 적는다.

3=> 객체 정의

proto 문법은 위에 남긴 proto 공식 문서를 참고하길 바란다.


3. Serializer 정의

class MySerializer: Serializer<Person> {    //1
override val defaultValue: Person    //2
get() = Person.getDefaultInstance()

override suspend fun readFrom(input: InputStream): Person {    //3
try {
return Person.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}

override suspend fun writeTo(t: Person, output: OutputStream) {    //4
t.writeTo(output)
}
}

val Context.personDataStore: DataStore<Person> by dataStore(    //5
fileName = "my_data",    //6
serializer = MySerializer()    //7
)

1=> Serializer 클래스는 데이터 유형을 읽고 쓰는 방법을 DataStore에 알린다.

2=> 아직 파일이 생성되지 않은 경우 사용할 Serializer의 기본값을 포함해야한다.

3=> DataStore로부터 데이터를 읽어온다. 역시나 suspend 함수다.

4=> DataStore에 데이터를 쓴다.

5=> dataStore로 만든 속성을 위임하여 DataStore 객체를 만든다.

6=> fileName 매개변수는 데이터를 저장하는데 사용할 파일을 DataStore에 알린다.

7=> serializer 매개변수는 정의한 serializer 클래스 이름을 DataStore에 알린다.


4. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<EditText
android:id="@+id/editTextTextPersonName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:ems="10"
android:inputType="textPersonName"
android:hint="Proto 입력창"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>


<Button
android:id="@+id/save_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Save"
app:layout_constraintEnd_toEndOf="@+id/editTextTextPersonName"
app:layout_constraintStart_toStartOf="@+id/editTextTextPersonName"
app:layout_constraintTop_toBottomOf="@+id/editTextTextPersonName" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="@+id/save_btn"
app:layout_constraintStart_toStartOf="@+id/save_btn"
app:layout_constraintTop_toBottomOf="@+id/save_btn"
tools:text="result_proto"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>


5. ProtoRepository 정의

class ProtoRepository(val context: Context) {
val TAG = this::class.java.simpleName

val readProto: Flow<Person> = context.personDataStore.data    //1
.catch { exception ->
if(exception is IOException) {
Log.d(TAG, exception.message.toString())
emit(Person.getDefaultInstance())
} else {
throw exception
}
}

suspend fun updateValue(firstName: String) {    //2
context.personDataStore.updateData { preference ->
preference.toBuilder().setFirstName(firstName).build()
}
}
}

1=> DataStore.data를 사용하여 저장된 객체에서 적절한 속성의 Flow를 노출합니다.

2 => Proto Datastore는 저장된 객체를 트랜잭션 방식으로 업데이트하는 updateData() 함수를 제공합니다. 


6. MainViewModel 정의

class MainViewModel(application: Application): AndroidViewModel(application) {
private val repository = ProtoRepository(application)

val firstName = repository.readProto.asLiveData()

fun updateValue(firstName : String) = viewModelScope.launch(Dispatchers.IO) {
repository.updateValue(firstName)
}
}

DataStore에서 쓰기 과정은 코루틴을 사용하여 비동기적으로 실행된다.

✋ asLiveData() 사용하기위해서 build.gradle(앱수준)에 아래와 같이 dependency를 추가한다

implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")


7. MainActivity 정의

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var userManager: UserManager
private var age = -1
private var frontName = ""
private var lastName = ""
private var gender = ""

private lateinit var mainViewModel: MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]    //1
mainViewModel.firstName.observe(this) {   //2
binding.textView.text = it.firstName
}

binding.saveBtn.setOnClickListener {    //3
val firstName = binding.editTextTextPersonName.text.toString()
mainViewModel.updateValue(firstName)
}
}

}

1=> mainViewModel 객체 가져오기

2=> DataStore에 저장된 값 관찰

3=> DataStore에 값 저장하기






Share:
Location: 대한민국

댓글 없음:

댓글 쓰기