[Kotlin/Android] SharedPreferences Delegate

어플리케이션을 개발하면서, SharedPreferences를 사용하는 경우가 많았다.

특히 SharedPreference를 사용하면서 반복적인 작업 (get/set작성) 에 의해 코드가 매우 길어지는 경우가 발생했다.

6주간 인턴십을 하면서 해당 방법을 적용해보고는 싶었지만 수정해야할 부분이 상당히 많았기 때문에 고민만 하고 실제로 적용하지는 않았다.

인턴십이 끝나고 어느정도 여유가 생긴 지금 Delegate를 통해 해당 문제를 개선해보고자 한다.

SharedPreferences란?

SharedPreferences란 데이터를 key-value 쌍으로 저장하고 검색하기 위한 도구이다.

어디에 사용할까?

일반적으로 앱을 종료하고 다시 시작해도 지속되어야 하는 기본 설정 값이나 앱의 설정 값을 저장하는 데 사용된다.

SharedPreferences의 문제

SharedPreferences를 사용하다 보면, 다음과 같은 반복적인 코드 작성이 발생하게 된다.

반복적인 상황

  1. 값을 저장한다.
  2. 값을 검색한다.

만약 초기 설정 값을 많이 사용하는 애플리케이션을 개발하게 된다면, 위와 같은 반복적인 상황에 의해 코드가 복잡해지게 된다.

다음과 같은 문제를 Delegate하여 해결해보고자 한다.

Delegate - 위임

위임이란 내가 가진 책임의 일부를 다른 객체에 위임하는 것이다.

주로 property에 대한 gettersetter를 제어하고자 할 때 사용이 된다.

Kotlin에서 Delegate를 통해 위의 반복적인 작업을 처리하는 Delegator를 설정하여 SharedPreferences를 관리하도록 하는 것이 이번 포스팅의 목표이다.

SharedPreferencesDelegate의 정의

위임을 위한 SharedPreferencesDelegate 클래스를 정의한다.

class SharedPreferencesDelegate<T>(  
    private val sharedPreferences: SharedPreferences,  
    private val key: String,  
    private val defaultValue: T) {}

속성에 대한 설명은 다음과 같다.

  • sharedPreferences: SharedPreferences 인스턴스.
  • key: SharedPreferences에서 값에 접근하기 위한 키.
  • defaultValue: 값이 없을 경우 사용할 기본값.

다음은 delegategettersetteroperator를 통해 정의한다.

operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
    // 프로퍼티 값을 반환하는 로직
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
    // 프로퍼티 값을 설정하는 로직
}

gettersetter를 위의 코드처럼 반드시 제너릭 형태로 정의할 필요는 없지만, SharedPreferences의 경우에는 특정 데이터 타입의 고정되어 있지 않고 여러 데이터 타입을 처리할 수 있어야 한다.

SharedPreferences에 저장할 수 있는 데이터 유형

저장할 수 있는 데이터 유형은 크게 6가지가 있다.

  1. Boolean: putBoolean()getBoolean() 메서드를 사용하여 불리언 값 저장 및 검색 가능.
  2. Int: putInt()getInt() 메서드를 사용하여 정수 값 저장 및 검색 가능.
  3. Long: putLong()getLong() 메서드를 사용하여 롱 값 저장 및 검색 가능.
  4. Float: putFloat()getFloat() 메서드를 사용하여 부동 소수점 값 저장 및 검색 가능.
  5. String: putString()getString() 메서드를 사용하여 문자열 값 저장 및 검색 가능.
  6. Set : putStringSet()getStringSet() 메서드를 사용하여 문자열 집합 저장 및 검색 가능.

다음과 같이 여러가지 타입의 처리를 하기 위해 제너릭을 사용한다

정의

이제 6가지 타입에 맞추어 gettersetter를 정의한다.

만약에 해당 타입이 정의될 수 없는 타입일 경우에는 에러를 발생시킨다.

operator fun getValue(thisRef: Any?, property: KProperty<*>): T {  
    return with(sharedPreferences) {  
        val data: Any = when (defaultValue) {  
            is Long -> getLong(key, defaultValue)  
            is String -> getString(key, defaultValue) ?: ""  
            is Int -> getInt(key, defaultValue)  
            is Boolean -> getBoolean(key, defaultValue)  
            is Float -> getFloat(key, defaultValue)  
            else -> throw IllegalArgumentException("해당 타입은 저장할 수 없습니다.")  
        }  
        data as T  
    }  
}  

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {  
    with(sharedPreferences.edit()) {  
        when (value) {  
            is Long -> putLong(key, value)  
            is String -> putString(key, value)  
            is Int -> putInt(key, value)  
            is Boolean -> putBoolean(key, value)  
            is Float -> putFloat(key, value)  
            else -> throw IllegalArgumentException("해당 타입은 저장할 수 없습니다.")  
        }.apply()  
    }  
}

전체적인 코드

class SharedPreferencesDelegate<T>(  
    private val sharedPreferences: SharedPreferences,  
    private val key: String,  
    private val defaultValue: T) {  

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {  
        return with(sharedPreferences) {  
            val data: Any = when (defaultValue) {  
                is Long -> getLong(key, defaultValue)  
                is String -> getString(key, defaultValue) ?: ""  
                is Int -> getInt(key, defaultValue)  
                is Boolean -> getBoolean(key, defaultValue)  
                is Float -> getFloat(key, defaultValue)  
                else -> throw IllegalArgumentException("해당 타입은 저장할 수 없습니다.")  
            }
            @Suppress("UNCHECKED_CAST")
            data as T  
        }  
    }  

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {  
        with(sharedPreferences.edit()) {  
            when (value) {  
                is Long -> putLong(key, value)  
                is String -> putString(key, value)  
                is Int -> putInt(key, value)  
                is Boolean -> putBoolean(key, value)  
                is Float -> putFloat(key, value)  
                else -> throw IllegalArgumentException("해당 타입은 저장할 수 없습니다.")  
            }.apply()  
        }  
    }  
}

Delegate 확장 함수

SharedPreference에 대한 Delegate를 생성하기 위해 다음과 같이 작성한다.

fun <T> SharedPreferences.delegate(  
    key: String,  
    defaultValue: T  
): SharedPreferencesDelegate<T> = SharedPreferencesDelegate(this, key, defaultValue)

사용하기

val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) 

// SharedPreferencesDelegate를 사용하여 delegate 생성
val nameValue: String by sharedPreferences.delegate("name", "") 

// 값에 접근 
val value = nameValue

// 값을 변경
nameValue = "Shino72"

위 코드에서 SharedPreferencesDelegatedelegate 확장 함수를 사용하여 name이라는 키와 기본값을 지정하여 delegate를 생성한다.

이렇게 하면 name 키에 저장된 값이 이미 존재한다면 해당 값을 불러올 것이고, 그렇지 않다면 기본값으로 설정된 빈 문자열을 반환한다.

그리고 이 값을 변수 value에 할당하여 사용할 수 있다.

잘 저장되어 있음을 볼 수 있다.

이렇게 하면 간단하게 사용할 수 있다!

PreferenceManager deprecated

PreferenceManager deprecatedapi 29 이후로 deprecated되었다.

build.gradle에 다음 코드를 추가하여 사용하거나

implementation 'androidx.preference:preference:1.2.0'

아니면,

val sharedPreferences: SharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)

다음 방법으로 대체한다.