Android에서는 Key-Value 형태의 비교적 적은 데이터를 저장하기 위한 용도로 SharedPreferences API를 제공하고 있습니다.
이를 통해 Room, Realm 혹은 파일 저장 등과 같은 방법을 쓰지 않아도 간단하게 디바이스에 데이터를 저장할 수 있습니다.
다음 코드는 Android에서 제공하는 기본적인 SharedPreferences 사용법입니다.
val sharedPref = getSharedPreferences("name", Context.MODE_PRIVATE)
with (sharedPref.edit()) {
putString("key", "value")
apply
}
위 방식의 경우 매번 SharedPreference 를 선언해주어야 하고 편집까지 하는 경우에는 코드가 더욱 길어지기도 합니다.
이러한 불편함을 해결하기 위해서 시도한 첫번째 방법은 별도의 클래스를 만들어 저장할 데이터에 해당하는 변수를 생성해두고 getter과 setter를 지정해주는 방법입니다.
class DataManager(
private val context: Context
) {
companion object {
const val DATA_KEY = "key"
}
private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
var data: String? = null
get() = prefs.getString(DATA_KEY, "")
set(value) {
with(prefs.edit()) {
putString(DATA_KEY, value)
apply()
}
field = value
}
}
앞서 만든 DataManager에서 조금 더 발전 시키기 위한 아이디어를 구상 해보았습니다.
다음과 같은 조건을 충족시키기 위해 by 키워드를 통해 코드를 작성 해보았습니다.
(본 글에서는 by 키워드를 이용한 방법에 대해서만 작성하고 설명에 대해서는 별도 포스트를 작성할 예정입니다.)
class StringPref(
private val sharedPref: SharedPreferences,
private val default: String = ""
) : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
return sharedPref.getString(property.name, default)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
with(sharedPref.edit()) {
putString(property.name, value)
apply()
}
}
}
class DataManager(
private val context: Context
) {
private val sharedPref = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
var userName: String? by StringPref(sharedPref)
}
위 코드를 보면 property의 name 값을 통해 변수명을 키값으로 사용할 수 있게 되었고 by 키워드를 사용하여 getValue와 setValue 메소드를 override 해줌으로서 getter와 setter의 정의가 필요하지 않게 되었습니다.
StringPref와 같이 IntPref, FloatPref 등 필요하다면 타입에 맞춰 각각 생성하여 사용할 수 있습니다.
SharedPreferences에서 제공하는 여러 타입들을 구현하여 코드를 작성하였고 이를 더 확장 시켜보기로 했습니다.
List와 Map 같은 형태의 타입도 직렬화를 통해 저장 해보았습니다.
직렬화는 Gson Library를 사용했습니다.
직렬화를 위해 다음과 같은 Kotlin Extension 메소드를 작성했습니다.
fun Any?.toJson(): String {
this?.let {
return Gson().toJson(it)
} ?: kotlin.run {
return ""
}
}
toJson 메소드를 통해 직렬화된 데이터를 SharedPreferences에 저장하고 Gson().fromJson() 메소드를 통해 데이터를 불러올 수 있었습니다.
class ListPref<T>(
private val sharedPref: SharedPreferences,
private val default: List<T> = arrayListOf()
) : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
return Gson().fromJson(
sharedPref.getString(property.name, default.toJson()),
object : TypeToken<T>() {}.type
)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
with(sharedPref.edit()) {
putString(property.name, value.toJson())
apply()
}
}
}
class DataManager(
private val context: Context
) {
private val sharedPref = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
var userName: List<String> by ListPref(sharedPref)
}
위 코드를 작성하면서 예외 처리, 불편함 등을 알아보기 위해 사이드 프로젝트에서 실제로 사용하고 코드를 계속 발전시키기 위해 Kref라는 이름의 라이브러리를 만들고 JitPack을 통해 배포 해보았습니다.
다음 포스트에서는 라이브러리 제작, README.md 작성, 배포 등의 과정에 대해 다뤄볼 예정입니다.