Skip to content

Instantly share code, notes, and snippets.

@jaredrummler
Created May 15, 2018 05:20
Show Gist options
  • Save jaredrummler/cae81cc576399ccf6897e064742b897a to your computer and use it in GitHub Desktop.
Save jaredrummler/cae81cc576399ccf6897e064742b897a to your computer and use it in GitHub Desktop.
Yet another SharedPreference helper written in Kotlin
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
class Prefs(
private val preferences: SharedPreferences,
private val encryptor: StringEncryptor? = null,
private val obfuscator: Obfuscator? = null
) {
/**
* Retrieve a String value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return The preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not a String
*/
fun get(key: String, defValue: String? = null): String? {
return encryptor?.run {
obfuscate(key).let {
return if (preferences.contains(it)) {
decrypt(preferences.getString(it, defValue))
} else defValue
}
} ?: preferences.getString(obfuscate(key), defValue)
}
/**
* Retrieve a boolean value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return Returns the preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not a boolean.
*/
fun get(key: String, defValue: Boolean = false): Boolean {
return preferences.getBoolean(obfuscate(key), defValue)
}
/**
* Retrieve a float value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return The preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not a float.
*/
fun get(key: String, defValue: Float = 0.0f): Float {
return preferences.getFloat(obfuscate(key), defValue)
}
/**
* Retrieve a double value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return Returns the preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not a long.
*/
fun get(key: String, defValue: Double = 0.0): Double {
obfuscate(key).let {
if (preferences.contains(it)) {
val bits = preferences.getLong(it, 0)
return java.lang.Double.longBitsToDouble(bits)
}
}
return defValue
}
/**
* Retrieve an int value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return Returns the preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not an int.
*/
fun get(key: String, defValue: Int = 0): Int {
return preferences.getInt(obfuscate(key), defValue)
}
/**
* Retrieve a long value from the preferences.
*
* @param key The name of the preference to retrieve.
* @param defValue Value to return if this preference does not exist.
* @return The preference value if it exists, or defValue.
* @throws ClassCastException If there is a preference with this name that is not a long.
*/
fun get(key: String, defValue: Long = 0L): Long {
return preferences.getLong(obfuscate(key), defValue)
}
/**
* Retrieve a set of String values from the preferences.
*
* Note that you *must not* modify the set instance returned by this call. The consistency of the stored
* data is not guaranteed if you do, nor is your ability to modify the instance at all.
*
* @param key The name of the preference to retrieve.
* @param defValues Values to return if this preference does not exist.
* @return The preference values if they exist, or defValues.
* @throws ClassCastException If there is a preference with this name that is not a Set.
*/
fun get(key: String, defValues: Set<String>? = null): Set<String>? {
return preferences.getStringSet(obfuscate(key), defValues)
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Boolean) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Float) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Double) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Int) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Long) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: Set<String>?) {
edit().put(key, value).apply()
}
/**
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
fun save(key: String, value: String) {
edit().put(key, value).apply()
}
/**
* Removes a preference from the SharedPreferences.
*
* @param key The name of the preference to remove.
*/
fun remove(key: String) {
edit().remove(key).apply()
}
/**
* Checks whether the preferences contains a preference.
*
* @param key The name of the preference to check.
* @return True if the preference exists in the preferences, otherwise false.
*/
fun contains(key: String): Boolean {
return preferences.contains(obfuscate(key))
}
/**
* Toggle a boolean value from true to false or vice versa.
*
* @param key The name of the boolean preference to toggle.
* @param defValue The default value for the preference, set if this preference does not exist.
*/
fun toggle(key: String, defValue: Boolean) {
save(obfuscate(key), !get(obfuscate(key), defValue))
}
/**
* Increases the value of an int preference.
*
* @param key The name of the preference to modify.
* @param increment The amount by which the preference should be increased.
* @return The value after incrementing the preference.
*/
fun increment(key: String, increment: Int = 1): Int {
val value = get(key, 0) + increment
save(key, value)
return value
}
/**
* Check if a preference key exists in the SharedPreferences. If it does not exist then the preference will be
* saved with the boolean value "true". This is useful to check if something has already occurred.
*
* @param key The name of the preference to modify/check.
* @return `true` if the preference has not yet been saved.
*/
fun isFirstTime(key: String): Boolean {
if (contains(key)) {
return false
}
save(key, true)
return true
}
/**
* Retrieve all values from the preferences.
*
* Note that you *must not* modify the collection returned by this method, or alter any
* of its contents. The consistency of your stored data is not guaranteed if you do.
*
* @return Returns a map containing a list of pairs key/value representing the preferences.
* @throws NullPointerException
*/
fun getAll(): Map<String, *> {
return preferences.all
}
/**
* Create a new Editor for these preferences, through which you can make modifications to the data in the
* preferences and atomically commit those changes back to the SharedPreferences object.
*
* Note that you *must* call [Editor.commit] to have any changes you perform in the Editor actually
* show up in the SharedPreferences.
*
* @return Returns a new instance of the [Editor], allowing you to modify the values in this
* SharedPreferences object.
*/
fun edit(): Editor {
return Editor(this)
}
private fun obfuscate(string: String) = obfuscator?.obfuscate(string) ?: string
private fun encrypt(string: String) = encryptor?.encrypt(string) ?: string
private fun decrypt(string: String) = encryptor?.decrypt(string) ?: string
/**
* Interface used for modifying values in a [SharedPreferences] object. All changes you make in an editor are
* batched, and not copied back to the original [SharedPreferences] until you call [commit] or
* [apply]
*/
@SuppressLint("CommitPrefEdits")
class Editor internal constructor(private val prefs: Prefs) {
private val editor = prefs.preferences.edit()
/**
* Set a String value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: String): Editor {
editor.putString(prefs.obfuscate(key), prefs.encrypt(value))
return this
}
/**
* Set a boolean value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: Boolean): Editor {
editor.putBoolean(prefs.obfuscate(key), value)
return this
}
/**
* Set a float value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: Float): Editor {
editor.putFloat(prefs.obfuscate(key), value)
return this
}
/**
* Set a double value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: Double): Editor {
// http://stackoverflow.com/a/18098090/1048340
editor.putLong(prefs.obfuscate(key), java.lang.Double.doubleToRawLongBits(value))
return this
}
/**
* Set an int value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: Int): Editor {
editor.putInt(prefs.obfuscate(key), value)
return this
}
/**
* Set a long value in the preferences editor, to be written back once [commit] or [apply] are
* called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun put(key: String, value: Long): Editor {
editor.putLong(prefs.obfuscate(key), value)
return this
}
/**
* Set a set of String values in the preferences editor, to be written back once [commit] or
* [apply] is called.
*
* @param key The name of the preference to modify.
* @param values The set of new values for the preference. Passing `null` for this argument is equivalent to calling
* [remove] with this key.
* @return A reference to the same Editor object, so you can
* chain put calls together.
*/
fun put(key: String, values: Set<String>?): Editor {
editor.putStringSet(prefs.obfuscate(key), values)
return this
}
/**
* Mark in the editor that a preference value should be removed, which will be done in the actual preferences
* once [commit] is called.
*
* Note that when committing back to the preferences, all removals are done first, regardless of whether you
* called remove before or after put methods on this editor.
*
* @param key The name of the preference to remove.
* @return A reference to the same Editor object, so you can chain put calls together.
*/
fun remove(key: String): Editor {
editor.remove(prefs.obfuscate(key))
return this
}
/**
* Commit your preferences changes back from this Editor to the [SharedPreferences] object it is editing.
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* Note that when two editors are modifying preferences at the same time, the last one to call apply wins.
*
* Unlike [commit], which writes its preferences out to persistent storage synchronously,
* `apply` commits its changes to the in-memory [SharedPreferences] immediately but starts an
* asynchronous commit to disk and you won't be notified of any failures. If another editor on this
* [SharedPreferences] does a regular [commit] while a `apply` is still outstanding, the
* [commit] will block until all async commits are completed as well as the commit itself.
*
*
* As [SharedPreferences] instances are singletons within a process, it's safe to replace any instance
* of [commit] with `apply` if you were already ignoring the return value.
*
* You don't need to worry about Android component lifecycles and their interaction with `apply()`
* writing to disk. The framework makes sure in-flight disk writes from `apply()` complete before
* switching states.
*
* The SharedPreferences.Editor interface isn't expected to be implemented directly. However, if
* you previously did implement it and are now getting errors about missing `apply()`, you can simply
* call [commit] from `apply()`.
*/
fun apply() {
editor.apply()
}
/**
* Commit your preferences changes back from this Editor to the [SharedPreferences] object it is editing.
* This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
*
* Note that when two editors are modifying preferences at the same time, the last one to call commit wins.
*
* If you don't care about the return value and you're using this from your application's main thread,
* consider using [apply] instead.
*
* @return True if the new values were successfully written to persistent storage.
*/
fun commit(): Boolean {
return editor.commit()
}
}
interface StringEncryptor {
/**
* Encrypt a String.
*
* @param plainText the String to encrypt.
* @return The encrypted String.
*/
fun encrypt(plainText: String): String
/**
* Decrypt a String.
*
* @param encryptedText The encrypted text using [decrypt].
* @return The original String.
*/
fun decrypt(encryptedText: String): String
}
/**
* Obfuscate preference keys
*/
interface Obfuscator {
/**
* Obfuscate a string.
*
* @param string
* the original string
* @return an obfuscated string.
*/
fun obfuscate(string: String): String
}
companion object {
private object Holder {
val INSTANCE: Prefs
get() {
if (::context.isInitialized) {
throw IllegalStateException("Prefs has not been initialized")
}
return Prefs(PreferenceManager.getDefaultSharedPreferences(context))
}
}
@SuppressLint("StaticFieldLeak")
private lateinit var context: Context
fun init(context: Context) {
this.context = context.applicationContext
}
@JvmStatic
val instance: Prefs by lazy { Holder.INSTANCE }
}
}
@the-kool-sk
Copy link

sample usage for this?

@PembaTamang
Copy link

PembaTamang commented Aug 9, 2020

I got an error ' lateinit property context has not been initialized' help @jaredrummler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment