Development record of developer who study hard everyday.

, , , ,

안드로이드 Preferences DataStore 사용법과 예제

 안드로이드 Preferences DataStore 사용법과 예제


안드로이드 개발 블로그

 MVVM 패턴을 공부하면서 함께 적용할 Jetpack Architecture에 대해 공부하고 있다.

왠만한건 한 번씩 다 봤지만, DataStore란 녀석은 본 적이 없어서 이번 기회에 제대로 공부하기로 했다.

앱이 종료되어도 저장되어야할 작은 데이터는 주로 SharedPreferences를 사용해왔다.

하지만 이제는 구글에서 DataStore로 이전하기를 권장하고있다.

SharedPreferences의 한계는 다음과 같다.

1. UI스레드에서 호출할 수 있도록 API가 설계되었지만, UI스레드를 블로킹해 ANR을 발생시킬 수 있다.

2. 다중스레드 환경에서 안전하지 않다.

3. type safety가 보장되지 않는다.

이러한 문제를 해결하기위해 DataStore가 만들어졌다.

그럼, 예제를 함께 살펴보자.

1. dependency 추가

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

}

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


2. Preferences DataStore 만들기

UserManager.kt 파일을 만들고 전역 프로퍼티로 아래의 코드를 추가해준다.

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

dataStore를 싱글톤으로 만들어서 DataStore<Preferences> 객체를 만듭니다.

name 매개변수는 Preferences DataStore의 이름입니다.


3. Preferences DataStore에서 읽고 쓰기

class UserManager(
private val dataStore: DataStore<Preferences>
) {
companion object {    //1
val USER_AGE_KEY = intPreferencesKey("USER_AGE")
val USER_FIRST_NAME_KEY = stringPreferencesKey("USER_FIRST_NAME")
val USER_LAST_NAME_KEY = stringPreferencesKey("USER_LAST_NAME")
val USER_GENDER_KEY = booleanPreferencesKey("USER_GENDER")
}
    
suspend fun storeUSer(
age: Int,
frontName: String,
lastName: String,
isMale: Boolean,
) {
dataStore.edit {    //2
it[USER_AGE_KEY] = age
it[USER_FIRST_NAME_KEY] = frontName
it[USER_LAST_NAME_KEY] = lastName
it[USER_GENDER_KEY] = isMale
}
}
    //3
val userAgeFlow: kotlinx.coroutines.flow.Flow<Int?> = dataStore.data.map {
it[USER_AGE_KEY]
}

val userFirstNameFlow: kotlinx.coroutines.flow.Flow<String?> = dataStore.data.map {
it[USER_FIRST_NAME_KEY]
}

val userLastNameFlow: kotlinx.coroutines.flow.Flow<String?> = dataStore.data.map {
it[USER_LAST_NAME_KEY]
}

val userGenderFlow: kotlinx.coroutines.flow.Flow<Boolean?> = dataStore.data.map {
it[USER_GENDER_KEY]
}
}

1=> 키값 모음

예를 들어, int값의 키를 정의하려면 intPreferencesKey()를 사용한다.


2=> Preferences DataStore에 값 쓰기

SharedPreferences와 비슷하게 edit() 함수를 사용하여 값을 쓴다.

참고로 DataStore는 알아서 코루틴을 사용하여 값을 저장시킨다.

따라서 suspend 함수로 선언한다.


3=> Preferences DataStore에서 값 읽기

DataStore는 값을 저장하거나 읽어올 때 코루틴을 사용하여 Flow로 결과를 전달한다.

DataStore.data 속성을 사용하여 Flow를 받고 map을 이용하여 값을 읽어왔다.


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">

<Button
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:padding="18dp"
android:text="Save user"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/switch_gender"
app:layout_constraintStart_toStartOf="@+id/switch_gender"
app:layout_constraintTop_toBottomOf="@+id/switch_gender" />

<EditText
android:id="@+id/et_lname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="이름"
app:layout_constraintEnd_toEndOf="@+id/et_fname"
app:layout_constraintStart_toStartOf="@+id/et_fname"
app:layout_constraintTop_toBottomOf="@+id/et_fname" />

<EditText
android:id="@+id/et_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="나이"
android:inputType="number"
app:layout_constraintEnd_toEndOf="@+id/et_lname"
app:layout_constraintStart_toStartOf="@+id/et_lname"
app:layout_constraintTop_toBottomOf="@+id/et_lname"
tools:layout_editor_absoluteY="317dp" />

<EditText
android:id="@+id/et_fname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:ems="10"
android:hint="나머지 이름"
app:layout_constraintEnd_toEndOf="@+id/tv_gender"
app:layout_constraintStart_toStartOf="@+id/tv_gender"
app:layout_constraintTop_toBottomOf="@+id/tv_gender"
tools:layout_editor_absoluteY="178dp" />

<TextView
android:id="@+id/tv_fname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="firstName"/>

<TextView
android:id="@+id/tv_lname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/tv_fname"
app:layout_constraintStart_toStartOf="@+id/tv_fname"
app:layout_constraintTop_toBottomOf="@+id/tv_fname"
tools:text="lastName"/>

<TextView
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/tv_lname"
app:layout_constraintStart_toStartOf="@+id/tv_lname"
app:layout_constraintTop_toBottomOf="@+id/tv_lname"
tools:text="age"/>

<TextView
android:id="@+id/tv_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/tv_age"
app:layout_constraintStart_toStartOf="@+id/tv_age"
app:layout_constraintTop_toBottomOf="@+id/tv_age" />

<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="성별"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="@+id/et_age"
app:layout_constraintStart_toStartOf="@+id/et_age"
app:layout_constraintTop_toBottomOf="@+id/et_age" />


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

데이터바인딩을 사용했다.(세팅 방법은 생략한다.)

간단하게 설명하면, editText와 switch에 firstName, lastName, 나이, 성별을 입력하고 저장버튼을 누르면 DataStore에 저장한다.

그리고 TextView에는 저장된 데이터를 바로 보여준다.


5. MainActivity.kt


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 = ""

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

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

userManager = UserManager(dataStore)

binding.run {
buttonSave()
observeData()
}
}

private fun ActivityMainBinding.buttonSave() {
btnSave.setOnClickListener {
frontName = etFname.text.toString()
lastName = etLname.text.toString()
age = etAge.text.toString().toInt()
val isMale = switchGender.isChecked

CoroutineScope(Dispatchers.IO).launch {
userManager.storeUSer(age, frontName, lastName, isMale)
}
}
}

private fun observeData() {
userManager.userAgeFlow.asLiveData().observe(this) {
if(it != null) {
age = it
binding.tvAge.text = it.toString()
}
}

userManager.userFirstNameFlow.asLiveData().observe(this) {
if (it != null) {
frontName = it
binding.tvFname.text = it
}
}

userManager.userLastNameFlow.asLiveData().observe(this) {
if (it != null) {
lastName = it
binding.tvLname.text = it
}
}

userManager.userGenderFlow.asLiveData().observe(this) {
if (it != null) {
gender = if (it) "남성" else "여성"
binding.tvGender.text = gender
}
}
}
}

observeData() 함수에 보면 Flow를 LiveData로 변환시켜서 관찰하는데 

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

위 종속성을 추가해주어야 asLiveData() 메소드를 사용가능하다.



Share:

댓글 없음:

댓글 쓰기