Created
November 13, 2019 21:30
-
-
Save jeffypooo/0cf4eee16d6bc998991fa3b373307120 to your computer and use it in GitHub Desktop.
Testable and decoupled preferences interface for Android apps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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