Version 2.0

import android.content.Context
import android.content.SharedPreferences
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlin.reflect.KProperty

/**
 * # Encrypted SharedPreferences Manager
 * @author Sergey Kalinovsky
 *
 * ## Dependency:
 *
 * androidx.security:security-crypto:VERSION (tested with 1.1.0-alpha02)
 *
 * ## Initialization:
 *
 * This manager must be initialized by application context in Application class:
 *
 * [EncryptedSharedPreferencesManager.init(this)][init]
 *
 * ## Usage in Kotlin code:
 *
 * Declare variable by Kotlin delegate.
 *
 *     var str by EncryptedSharedPreferencesManager.prefs.string("SHARED_PREFERENCES_KEY")
 *
 * after that property can be used as usual variable (str = "some text" or println(str))
 *
 * ## Usage in Java code:
 *
 *     EncryptedSharedPreferencesManager.setString("SHARED_PREFERENCES_KEY", myText)
 *
 * or
 *
 *     text = EncryptedSharedPreferencesManager.getString("SHARED_PREFERENCES_KEY", null)
 *
 * Second parameter (default value) can be skipped
 */
@Suppress("unused")
object EncryptedSharedPreferencesManager {

    //region Init

    /**
     * Shared preferences instance
     */
    lateinit var prefs: SharedPreferences

    /**
     * Initialization method. Must be called in inheritor of [Application][android.app.Application] class
     * @param context application context
     */
    fun init(context: Context) {
        if (::prefs.isInitialized) throw IllegalStateException("EncryptedSharedPreferencesManager already initialized!")
        val spec = KeyGenParameterSpec.Builder(
            "_androidx_security_master_key_",
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(256)
            .build()
        val masterKey: MasterKey = MasterKey.Builder(context)
            .setKeyGenParameterSpec(spec)
            .build()
        prefs = EncryptedSharedPreferences.create(
            context,
            "secret_shared_prefs",
            masterKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )
    }

    // endregion
    // region Java methods

    /**
     * Getter for Shared Preferences boolean value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getBoolean(name: String) = getBoolean(name, false)
    /**
     * Getter for Shared Preferences boolean value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getBoolean(name: String, defValue: Boolean) = prefs.getBoolean(name, defValue)
    /**
     * Setter for Shared Preferences boolean value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setBoolean(name: String, value: Boolean) = prefs.edit().putBoolean(name, value).apply()

    /**
     * Getter for Shared Preferences float value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getFloat(name: String) = getFloat(name, 0f)
    /**
     * Getter for Shared Preferences float value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getFloat(name: String, defValue: Float) = prefs.getFloat(name, defValue)
    /**
     * Setter for Shared Preferences float value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setFloat(name: String, value: Float) = prefs.edit().putFloat(name, value).apply()

    /**
     * Getter for Shared Preferences int value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getInt(name: String) = getInt(name, 0)
    /**
     * Getter for Shared Preferences int value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getInt(name: String, defValue: Int) = prefs.getInt(name, defValue)
    /**
     * Setter for Shared Preferences int value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setInt(name: String, value: Int) = prefs.edit().putInt(name, value).apply()

    /**
     * Getter for Shared Preferences long value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getLong(name: String) = getLong(name, 0L)
    /**
     * Getter for Shared Preferences long value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getLong(name: String, defValue: Long) = prefs.getLong(name, defValue)
    /**
     * Setter for Shared Preferences long value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setLong(name: String, value: Long) = prefs.edit().putLong(name, value).apply()

    /**
     * Getter for String Shared Preferences value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getString(name: String) = getString(name, null)
    /**
     * Getter for String Shared Preferences value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getString(name: String, defValue: String?) = prefs.getString(name, defValue)
    /**
     * Setter for String Shared Preferences value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setString(name: String, value: String) = prefs.edit().putString(name, value).apply()

    /**
     * Getter for Set<String> Shared Preferences value
     * @param name shared preferences key
     * @return Shared Preferences value
     */
    @JvmStatic fun getStringSet(name: String): Set<String>? = getStringSet(name, null)
    /**
     * Getter for Set<String> Shared Preferences value
     * @param name shared preferences key
     * @param defValue default value
     * @return Shared Preferences value
     */
    @JvmStatic fun getStringSet(name: String, defValue: Set<String>?): Set<String>? = prefs.getStringSet(name, defValue)
    /**
     * Setter for Set<String> Shared Preferences value
     * @param name shared preferences key
     * @param value value to store in Shared Preferences
     */
    @JvmStatic fun setStringSet(name: String, value: Set<String>?) = prefs.edit().putStringSet(name, value).apply()

    // endregion
    // region Kotlin extensions

    /**
     * Delegate for accessing to Shared Preferences Boolean value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.boolean(name: String, defValue: Boolean = false) = BooleanPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences Float value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.float(name: String, defValue: Float = 0f) = FloatPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences Int value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.int(name: String, defValue: Int = 0) = IntPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences Long value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.long(name: String, defValue: Long = 0L) = LongPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences String value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.string(name: String, defValue: String = "") = StringPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences String? value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.nullableString(name: String, defValue: String? = null) = NullableStringPrefs(name, defValue, this)
    /**
     * Delegate for accessing to Shared Preferences Set<String>? value
     * @param name shared preferences key
     * @param defValue default value. Can be skipped
     */
    fun SharedPreferences.stringSet(name: String, defValue: Set<String> = setOf()) = StringSetPrefs(name, defValue, this)

    // endregion
    // region Kotlin delegates

    class BooleanPrefs(private val name: String, private val defValue: Boolean, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>) = prefs.getBoolean(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, b: Boolean) = prefs.edit().putBoolean(name, b).apply()
    }

    class FloatPrefs(private val name: String, private val defValue: Float, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>) = prefs.getFloat(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, f: Float) = prefs.edit().putFloat(name, f).apply()
    }

    class IntPrefs(private val name: String, private val defValue: Int, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>) = prefs.getInt(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, i: Int) = prefs.edit().putInt(name, i).apply()
    }

    class LongPrefs(private val name: String, private val defValue: Long, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>) = prefs.getLong(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, l: Long) = prefs.edit().putLong(name, l).apply()
    }

    class StringPrefs(private val name: String, private val defValue: String, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>): String = prefs.getString(name, defValue) ?: defValue
        operator fun setValue(thisRef: Any, property: KProperty<*>, s: String) = prefs.edit().putString(name, s).apply()
    }

    class NullableStringPrefs(private val name: String, private val defValue: String?, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>): String? = prefs.getString(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, s: String?) = prefs.edit().putString(name, s).apply()
    }

    class StringSetPrefs(private val name: String, private val defValue: Set<String>, private val prefs: SharedPreferences) {
        operator fun getValue(thisRef: Any, property: KProperty<*>): Set<String>? = prefs.getStringSet(name, defValue)
        operator fun setValue(thisRef: Any, property: KProperty<*>, ss: Set<String>?) = prefs.edit().putStringSet(name, ss).apply()
    }

    // endregion
}

Leave a Reply