Coverage Summary for Class: BaseSharedPreference (kr.open.library.simple_ui.core.local.base)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| BaseSharedPreference |
100%
(36/36)
|
95%
(19/20)
|
100%
(54/54)
|
99.5%
(380/382)
|
| BaseSharedPreference$booleanPref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| BaseSharedPreference$commitDoWork$2 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(84/84)
|
| BaseSharedPreference$Companion |
|
| BaseSharedPreference$doublePref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| BaseSharedPreference$floatPref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| BaseSharedPreference$intPref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| BaseSharedPreference$longPref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(17/17)
|
| BaseSharedPreference$removeAllCommit$$inlined$commitDoWork$1 |
0%
(0/1)
|
|
| BaseSharedPreference$removeAtCommit$$inlined$commitDoWork$1 |
0%
(0/1)
|
|
| BaseSharedPreference$saveCommit$$inlined$commitDoWork$1 |
0%
(0/1)
|
|
| BaseSharedPreference$stringPref$1 |
100%
(3/3)
|
|
100%
(3/3)
|
100%
(15/15)
|
| Total |
94.8%
(55/58)
|
95%
(19/20)
|
100%
(73/73)
|
99.6%
(564/566)
|
package kr.open.library.simple_ui.core.local.base
import android.content.Context
import android.content.SharedPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kr.open.library.simple_ui.core.logcat.Logx
import java.lang.Double.doubleToRawLongBits
import java.lang.Double.longBitsToDouble
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* SharedPreferences 접근을 공통화하기 위한 기반 클래스입니다.<br><br>
* Base class that exposes delegate builders and thread-safe commit utilities for SharedPreferences.<br>
*
* 이 클래스는 반복되는 읽기/쓰기 보일러플레이트를 줄이고, 타입 안전한 delegate와
* coroutine 친화적인 commit 유틸을 한곳에 모으기 위해 만들어졌습니다.<br><br>
* This class exists to reduce repetitive SharedPreferences boilerplate and to centralize
* type-safe delegates with coroutine-friendly commit utilities.<br>
*
* 사용 방법은 다음과 같습니다.<br>
* 1. groupKey를 지정해 이 클래스를 상속합니다.<br>
* 2. stringPref, intPref 같은 제공 delegate로 프로퍼티를 정의합니다.<br><br>
* Usage:<br>
* 1. Extend this class with a preference group key.<br>
* 2. Define properties using provided delegates such as stringPref or intPref.<br>
*
* Example:<br>
* ```
* class UserPreference(ctx: Context) : BaseSharedPreference(ctx, "user") {
* var userName by stringPref("user_name", "")
* var userAge by intPref("user_age", 0)
* }
* ```
*
* @param context 애플리케이션 범위 SharedPreferences를 얻기 위한 Android 컨텍스트입니다.<br><br>
* Android context used to obtain application-level SharedPreferences.<br>
* @param groupKey SharedPreferences 파일 이름입니다.<br><br>
* Preference file name.<br>
* @param sharedPrivateMode 파일 모드이며 기본값은 [Context.MODE_PRIVATE]입니다.<br><br>
* File mode, defaults to [Context.MODE_PRIVATE].<br>
*/
public abstract class BaseSharedPreference(
context: Context,
groupKey: String,
sharedPrivateMode: Int = Context.MODE_PRIVATE
) {
companion object {
private const val DOUBLE_TYPE = "_DOUBLE_"
}
protected val sp: SharedPreferences by lazy {
context.applicationContext.getSharedPreferences(groupKey, sharedPrivateMode)
}
protected val commitMutex: Mutex by lazy { Mutex() }
/**
* Builds a nullable `String` delegate bound to [key] with an optional default.<br><br>
* [key]와 기본값을 바인딩한 Nullable `String` 위임자를 만듭니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback string when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 문자열입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun stringPref(key: String, defaultValue: String? = null) = object : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? = getString(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) = saveApply(key, value)
}
/**
* Builds an `Int` delegate bound to [key] with [defaultValue].<br><br>
* [key]와 [defaultValue]를 바인딩한 `Int` 위임자를 만듭니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback integer when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 정수입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun intPref(key: String, defaultValue: Int) = object : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int = getInt(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) = saveApply(key, value)
}
/**
* Builds a `Boolean` delegate bound to [key] with [defaultValue].<br><br>
* [key]와 [defaultValue]를 바인딩한 `Boolean` 위임자를 만듭니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback boolean when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 불리언입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun booleanPref(key: String, defaultValue: Boolean) = object : ReadWriteProperty<Any?, Boolean> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean = getBoolean(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) = saveApply(key, value)
}
/**
* Builds a `Long` delegate bound to [key] with [defaultValue].<br><br>
* [key]와 [defaultValue]를 바인딩한 `Long` 위임자를 만듭니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback long when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Long 값입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun longPref(key: String, defaultValue: Long) = object : ReadWriteProperty<Any?, Long> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Long = getLong(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) = saveApply(key, value)
}
/**
* Builds a `Float` delegate bound to [key] with [defaultValue].<br><br>
* [key]와 [defaultValue]를 바인딩한 `Float` 위임자를 만듭니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback float when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Float 값입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun floatPref(key: String, defaultValue: Float) = object : ReadWriteProperty<Any?, Float> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Float = getFloat(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Float) = saveApply(key, value)
}
/**
* Builds a `Double` delegate bound to [key] with [defaultValue], storing raw bits in `Long`.<br><br>
* [key]와 [defaultValue]를 바인딩한 `Double` 위임자를 만들고 내부적으로 `Long` 비트로 저장합니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* SharedPreferences에 사용할 키입니다.<br>
* @param defaultValue Fallback double when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Double 값입니다.<br>
* @return Read/write property delegate backed by SharedPreferences.<br><br>
* SharedPreferences를 기반으로 하는 읽기·쓰기 위임자를 반환합니다.<br>
*/
protected fun doublePref(key: String, defaultValue: Double) = object : ReadWriteProperty<Any?, Double> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Double = getDouble(key, defaultValue)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) = saveApply(key, value)
}
/**
* Reads a nullable `String` from preferences.<br><br>
* SharedPreferences에서 Nullable `String` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback string when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 문자열입니다.<br>
* @return The stored string or [defaultValue].<br><br>
* 저장된 문자열 또는 [defaultValue].<br>
*/
protected fun getString(key: String, defaultValue: String?): String? = sp.getString(key, defaultValue)
/**
* Reads an `Int` from preferences.<br><br>
* SharedPreferences에서 `Int` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback integer when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 정수입니다.<br>
* @return The stored integer or [defaultValue].<br><br>
* 저장된 정수 또는 [defaultValue].<br>
*/
protected fun getInt(key: String, defaultValue: Int): Int = sp.getInt(key, defaultValue)
/**
* Reads a `Float` from preferences.<br><br>
* SharedPreferences에서 `Float` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback float when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Float 값입니다.<br>
* @return The stored float or [defaultValue].<br><br>
* 저장된 Float 값 또는 [defaultValue].<br>
*/
protected fun getFloat(key: String, defaultValue: Float): Float = sp.getFloat(key, defaultValue)
/**
* Reads a `Boolean` from preferences.<br><br>
* SharedPreferences에서 `Boolean` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback boolean when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 불리언입니다.<br>
* @return The stored boolean or [defaultValue].<br><br>
* 저장된 불리언 또는 [defaultValue].<br>
*/
protected fun getBoolean(key: String, defaultValue: Boolean): Boolean = sp.getBoolean(key, defaultValue)
/**
* Reads a `Long` from preferences.<br><br>
* SharedPreferences에서 `Long` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback long when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Long 값입니다.<br>
* @return The stored long or [defaultValue].<br><br>
* 저장된 Long 값 또는 [defaultValue].<br>
*/
protected fun getLong(key: String, defaultValue: Long): Long = sp.getLong(key, defaultValue)
/**
* Reads a `Set<String>` from preferences.<br><br>
* SharedPreferences에서 `Set<String>` 값을 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback set when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Set입니다.<br>
* @return The stored set or [defaultValue].<br><br>
* 저장된 Set 또는 [defaultValue].<br>
*/
protected fun getSet(key: String, defaultValue: Set<String>?): Set<String>? =
sp.getStringSet(key, defaultValue)?.toSet()
/**
* Reads a `Double` by mapping to raw long bits stored with a suffix.<br><br>
* 접미사를 덧붙여 저장된 `Long` 비트를 다시 `Double`로 변환해 읽어옵니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 읽어올 키입니다.<br>
* @param defaultValue Fallback double when the key has no value.<br><br>
* 값이 없을 때 사용할 기본 Double 값입니다.<br>
* @return The stored double or [defaultValue].<br><br>
* 저장된 Double 값 또는 [defaultValue].<br>
*/
protected fun getDouble(key: String, defaultValue: Double): Double =
longBitsToDouble(sp.getLong(key + DOUBLE_TYPE, doubleToRawLongBits(defaultValue)))
/**
* Writes the provided [value] into the editor, handling primitive and Set<String> types.<br><br>
* 프리미티브 및 `Set<String>` 타입을 처리하면서 [value]를 에디터에 기록합니다.<br>
*
* @param key Preference entry key.<br><br>
* SharedPreferences 키입니다.<br>
* @param value Value to store; unsupported types remove the key.<br><br>
* 저장할 값이며 지원되지 않는 타입이면 키를 삭제합니다.<br>
* @return Same [SharedPreferences.Editor] for chaining.<br><br>
* 체이닝을 위한 동일한 [SharedPreferences.Editor]를 반환합니다.<br>
*/
protected fun SharedPreferences.Editor.putValue(key: String, value: Any?): SharedPreferences.Editor = when (value) {
is String -> putString(key, value)
is Boolean -> putBoolean(key, value)
is Float -> putFloat(key, value)
is Int -> putInt(key, value)
is Double -> putLong(key + DOUBLE_TYPE, doubleToRawLongBits(value))
is Long -> putLong(key, value)
is Set<*> -> {
val stringSet = value.filterIsInstance<String>().toSet()
if (stringSet.size != value.size) {
Logx.e("[ERROR] Set<*> is not Set<String>. Key: $key, Value: $value")
throw ClassCastException("[ERROR] Set<*> is not Set<String>. Key: $key, Value: $value")
}
putStringSet(key, stringSet)
}
else -> {
if (value != null) {
Logx.e("Unsupported value type: $key, ${value.javaClass}")
}
remove(key)
}
}
/**
* Returns a fresh [SharedPreferences.Editor] instance.<br><br>
* 새로운 [SharedPreferences.Editor] 인스턴스를 반환합니다.<br>
*
* @return A new editor instance.<br><br>
* 새로운 에디터 인스턴스.<br>
*/
protected fun getEditor(): SharedPreferences.Editor = sp.edit()
/**
* Applies pending edits without another mutation.<br><br>
* 추가 수정 없이 보류 중인 변경 사항을 적용합니다.<br>
*/
protected fun saveApply(): Unit = sp.edit().apply()
/**
* Applies a single [key]/[value] mutation asynchronously.<br><br>
* [key]/[value] 쌍을 비동기적으로 적용합니다.<br>
*
* @param key SharedPreferences entry key.<br><br>
* 저장할 키입니다.<br>
* @param value Value to store.<br><br>
* 저장할 값입니다.<br>
*/
protected fun saveApply(key: String, value: Any?) = sp.edit().putValue(key, value).apply()
/**
* Commits a [key]/[value] mutation using a coroutine-friendly mutex.<br><br>
* 코루틴에 안전한 뮤텍스를 사용해 [key]/[value] 변경을 커밋합니다.<br>
*
* @return true when `commit()` succeeded, otherwise false.<br><br>
* `commit()`이 성공하면 true, 아니면 false를 반환합니다.<br>
*/
protected suspend fun saveCommit(key: String, value: Any?): Boolean = commitDoWork { putValue(key, value) }
/**
* Runs [doWork] inside `Dispatchers.IO` and mutual exclusion, then commits synchronously.<br><br>
* `Dispatchers.IO`와 상호 배제를 사용해 [doWork]를 실행한 뒤 동기 커밋합니다.<br>
*
* @return Result of `commit()` execution.<br><br>
* `commit()` 실행 결과를 반환합니다.<br>
*/
protected suspend inline fun commitDoWork(crossinline doWork: SharedPreferences.Editor.() -> Unit): Boolean =
withContext(Dispatchers.IO) { commitMutex.withLock { sp.edit().apply { doWork() }.commit() } }
/**
* Clears all entries using `apply()`. Use when asynchronous removal is acceptable.<br><br>
* `apply()`를 사용해 모든 항목을 삭제하며, 비동기 삭제가 허용될 때 이용합니다.<br>
*/
protected fun removeAllApply() = sp.edit().clear().apply()
/**
* Removes a specific key using `apply()`.<br><br>
* 특정 키를 `apply()` 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
private fun removeAt(key: String) = sp.edit().remove(key).apply()
/**
* Removes an `Int` entry using [removeAt].<br><br>
* [removeAt]을 통해 `Int` 항목을 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtInt(key: String) = removeAt(key)
/**
* Removes a `String` entry using [removeAt].<br><br>
* [removeAt]을 통해 `String` 항목을 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtString(key: String) = removeAt(key)
/**
* Removes a `Float` entry using [removeAt].<br><br>
* [removeAt]을 통해 `Float` 항목을 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtFloat(key: String) = removeAt(key)
/**
* Removes a `Long` entry using [removeAt].<br><br>
* [removeAt]을 통해 `Long` 항목을 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtLong(key: String) = removeAt(key)
/**
* Removes a `Boolean` entry using [removeAt].<br><br>
* [removeAt]을 통해 `Boolean` 항목을 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtBoolean(key: String) = removeAt(key)
/**
* Removes a `Double` entry by deleting the suffixed key.<br><br>
* 접미사가 붙은 키를 삭제해 `Double` 항목을 제거합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
*/
protected fun removeAtDouble(key: String) = removeAt(key + DOUBLE_TYPE)
/**
* Clears all entries with a synchronous commit.<br><br>
* 모든 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAllCommit(): Boolean = commitDoWork { clear() }
/**
* Removes a specific key with synchronous commit.<br><br>
* 특정 키를 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
private suspend fun removeAtCommit(key: String): Boolean = commitDoWork { remove(key) }
/**
* Removes an `Int` entry with synchronous commit.<br><br>
* `Int` 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAtIntCommit(key: String): Boolean = removeAtCommit(key)
/**
* Removes a `Float` entry with synchronous commit.<br><br>
* `Float` 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAtFloatCommit(key: String): Boolean = removeAtCommit(key)
/**
* Removes a `Long` entry with synchronous commit.<br><br>
* `Long` 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAtLongCommit(key: String): Boolean = removeAtCommit(key)
/**
* Removes a `String` entry with synchronous commit.<br><br>
* `String` 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAtStringCommit(key: String): Boolean = removeAtCommit(key)
/**
* Removes a `Double` entry (suffixed key) with synchronous commit.<br><br>
* 접미사 키를 사용하는 `Double` 항목을 동기 커밋 방식으로 삭제합니다.<br>
*
* @param key SharedPreferences entry key to remove.<br><br>
* 삭제할 키입니다.<br>
* @return `true` if commit succeeded, `false` otherwise.<br><br>
* 커밋이 성공하면 `true`, 실패하면 `false`.<br>
*/
protected suspend fun removeAtDoubleCommit(key: String): Boolean = removeAtCommit(key + DOUBLE_TYPE)
}