Skip to content

Instantly share code, notes, and snippets.

@jeffypooo
Created November 13, 2019 21:30
Show Gist options
  • Save jeffypooo/0cf4eee16d6bc998991fa3b373307120 to your computer and use it in GitHub Desktop.
Save jeffypooo/0cf4eee16d6bc998991fa3b373307120 to your computer and use it in GitHub Desktop.
Testable and decoupled preferences interface for Android apps
/**
* Data store for application preferences.
*/
interface AppPreferences {
/**
* Get the preference value for the given key.
* @param T The value type.
*/
operator fun <T : Any> get(key: String): T?
/**
* Set a preference value for the given key.
* @param value the new value.
*/
operator fun <T : Any> set(key: String, value: T)
/**
* Check whether this store contains a preference entry for the given key or not.
*/
fun containsKey(key: String): Boolean
/**
* Observe the preference value for the given key.
* @return an [Observable] that emits the preference value as it changes.
*/
fun <T> observe(key: String): Observable<T>
/**
* All stored key/value pairs.
*/
val all: Map<String, *>
}
class SharedAppPreferences(context: Context) : AppPreferences, SharedPreferences.OnSharedPreferenceChangeListener {
override fun <T : Any> get(key: String): T? = throwIfClosed {
vlog(TAG, "get(key: $key)")
sharedPrefs!!.all[key] as? T
}
override fun <T : Any> set(key: String, value: T) = throwIfClosed {
vlog(TAG, "set(key: $key, value: $value)")
sharedPrefs.edit {
when (value) {
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
is Boolean -> putBoolean(key, value)
is String -> putString(key, value)
else -> error("type not supported: ${value.javaClass}")
}
}
}
override fun containsKey(key: String): Boolean = throwIfClosed { sharedPrefs.contains(key) }
override fun <T> observe(key: String): Observable<T> = throwIfClosed {
vlog(TAG, "observe(key: $key)")
changeSubjects.getOrPut(key) { BehaviorSubject.create() }.map { it as T }
}
override val all: Map<String, *>
get() = throwIfClosed {
vlog(TAG, "all: get()")
sharedPrefs.all
}
override val isClosed: Boolean
get() = closed.get()
override fun close() {
vlog(TAG, "close()")
sharedPrefs?.unregisterOnSharedPreferenceChangeListener(this)
changeSubjects.run {
values.forEach { it.onComplete() }
clear()
}
closed.getAndSet(true)
}
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
vlog(TAG, "onSharedPreferenceChanged(key: $key)")
changeSubjects.getOrPut(key) { BehaviorSubject.create() }.onNext(prefs.all[key]!!)
}
private val closed = AtomicBoolean(false)
private val changeSubjects = mutableMapOf<String, BehaviorSubject<Any>>()
private val sharedPrefs = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE).apply {
registerOnSharedPreferenceChangeListener(this@SharedAppPreferences)
}
companion object {
private const val TAG = "SharedAppPreferences"
private const val FILE_NAME = "mypreferencesfile"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment