Skip to content

Instantly share code, notes, and snippets.

@wispborne
Last active July 21, 2022 03:47
Show Gist options
  • Save wispborne/b83e1744e8435a2c8cba262c1179f1a8 to your computer and use it in GitHub Desktop.
Save wispborne/b83e1744e8435a2c8cba262c1179f1a8 to your computer and use it in GitHub Desktop.
Android SharedPreferences helper class for Kotlin. Easy-to-use delegated properties, automatic database creation, and listening for property changes.

Usage

Wrapper class for SharedPreferences, leveraging the awesome delegate syntax in Kotlin.

Example preferences definition:

class UserPreferences : Preferences() {
 var emailAccount by stringPref()
 var allowAnonymousLogging by booleanPref()
}

Example usage:

preferences.allowAnonymousLogging = false

Example listener (not exactly the cleanest-looking but it works):

preferences.addListener(object : Preferences.SharedPrefsListener {
    override fun onSharedPrefChanged(property: KProperty<*>) {
        if (UserPreferences::allowAnonymousLogging.name == property.name) {
            initLogging()
        }
    }
})
import android.content.Context
import android.content.SharedPreferences
import kotlin.reflect.KProperty
/**
* Represents a single [SharedPreferences] file.
*
* Usage:
*
* ```kotlin
* class UserPreferences : Preferences() {
* var emailAccount by stringPref()
* var showSystemAppsPreference by booleanPref()
* }
* ```
*/
// Ignore unused warning. This class needs to handle all data types, regardless of whether the method is used.
// Allow unchecked casts - we can blindly trust that data we read is the same type we saved it as..
@Suppress("UNCHECKED_CAST", "unused")
abstract class Preferences(private var context: Context, private val name: String? = null) {
private val prefs: SharedPreferences by lazy {
context.getSharedPreferences(name ?: javaClass.simpleName, Context.MODE_PRIVATE)
}
private val listeners = mutableListOf<SharedPrefsListener>()
abstract class PrefDelegate<T>(val prefKey: String?) {
abstract operator fun getValue(thisRef: Any?, property: KProperty<*>): T
abstract operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
}
interface SharedPrefsListener {
fun onSharedPrefChanged(property: KProperty<*>)
}
fun addListener(sharedPrefsListener: SharedPrefsListener) {
listeners.add(sharedPrefsListener)
}
fun removeListener(sharedPrefsListener: SharedPrefsListener) {
listeners.remove(sharedPrefsListener)
}
fun clearListeners() = listeners.clear()
enum class StorableType {
String,
Int,
Float,
Boolean,
Long,
StringSet
}
inner class GenericPrefDelegate<T>(prefKey: String? = null, private val defaultValue: T?, val type: StorableType) :
PrefDelegate<T?>(prefKey) {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
when (type) {
StorableType.String ->
prefs.getString(prefKey ?: property.name, defaultValue as String?) as T?
StorableType.Int ->
prefs.getInt(prefKey ?: property.name, defaultValue as Int) as T?
StorableType.Float ->
prefs.getFloat(prefKey ?: property.name, defaultValue as Float) as T?
StorableType.Boolean ->
prefs.getBoolean(prefKey ?: property.name, defaultValue as Boolean) as T?
StorableType.Long ->
prefs.getLong(prefKey ?: property.name, defaultValue as Long) as T?
StorableType.StringSet ->
prefs.getStringSet(prefKey ?: property.name, defaultValue as Set<String>) as T?
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
when (type) {
StorableType.String -> {
prefs.edit().putString(prefKey ?: property.name, value as String?).apply()
onPrefChanged(property)
}
StorableType.Int -> {
prefs.edit().putInt(prefKey ?: property.name, value as Int).apply()
onPrefChanged(property)
}
StorableType.Float -> {
prefs.edit().putFloat(prefKey ?: property.name, value as Float).apply()
onPrefChanged(property)
}
StorableType.Boolean -> {
prefs.edit().putBoolean(prefKey ?: property.name, value as Boolean).apply()
onPrefChanged(property)
}
StorableType.Long -> {
prefs.edit().putLong(prefKey ?: property.name, value as Long).apply()
onPrefChanged(property)
}
StorableType.StringSet -> {
prefs.edit().putStringSet(prefKey ?: property.name, value as Set<String>).apply()
onPrefChanged(property)
}
}
}
}
fun stringPref(prefKey: String? = null, defaultValue: String? = null) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.String)
fun intPref(prefKey: String? = null, defaultValue: Int = 0) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.Int)
fun floatPref(prefKey: String? = null, defaultValue: Float = 0f) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.Float)
fun booleanPref(prefKey: String? = null, defaultValue: Boolean = false) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.Boolean)
fun longPref(prefKey: String? = null, defaultValue: Long = 0L) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.Long)
fun stringSetPref(prefKey: String? = null, defaultValue: Set<String> = HashSet()) =
GenericPrefDelegate(prefKey, defaultValue, StorableType.StringSet)
private fun onPrefChanged(property: KProperty<*>) {
listeners.forEach { it.onSharedPrefChanged(property) }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment