From 6dfd74b403eaead5ae268e95b09bb03f6dcd77fd Mon Sep 17 00:00:00 2001 From: Marcel Vojtkovszky Date: Tue, 27 Jul 2021 11:02:55 +0200 Subject: [PATCH] * added InMemorySharedPreferences implementation * SharedPreferences are now required in constructor of SharedPreferencesManager --- README.md | 26 ++- .../ExampleActivity.kt | 19 +- library/build.gradle | 2 +- .../InMemorySharedPreferences.kt | 199 ++++++++++++++++++ .../SharedPreferencesManager.kt | 25 +-- 5 files changed, 235 insertions(+), 36 deletions(-) create mode 100644 library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/InMemorySharedPreferences.kt diff --git a/README.md b/README.md index 14eb89e..f368e80 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,33 @@ # SharedPreferencesManager -Simplifies use of Android's SharedPreferences and allows storing of objects and list of objects. +Simplifies use of Android's SharedPreferences and allows storing of custom objects and list of objects. ## How does it work? -1. Create an instance of SharedPreferencesManager +1. Create an instance of Android's `SharedPreferences` ``` kotlin -preferencesManager = SharedPreferencesManager(applicationContext) +val sharedPreferences = applicationContext.getSharedPreferences("myCustomFileName", Context.MODE_PRIVATE) +``` + +1. Create an instance of `SharedPreferencesManager` with it. +``` kotlin +preferencesManager = SharedPreferencesManager(sharedPreferences) ``` or if you require more options, you could do something like ``` kotlin preferencesManager = SharedPreferencesManager( - context = applicationContext, - fileKey = "myCustomFileName", - operatingMode = Context.MODE_PRIVATE, + sharedPreferences = sharedPreferences json = Json { isLenient = true }, errorListener = { it.printStackTrace() } ) ``` +additionally, you can use provided `InMemorySharedPreferences`. This will persist all data in memory +only and omit using files. Useful for testing without mocking the context or if there is no need to +persist data outside of application lifecycle. +``` kotlin +val sharedPreferences = InMemorySharedPreferences() +``` + 2. Use any of the public methods to retrieve or save data. Any put call automatically applies changes. ``` kotlin fun getBoolean(key: String, defaultValue: Boolean): Boolean @@ -41,8 +51,8 @@ fun getAll(): MutableMap? fun remove(key: String) ``` -Note that [getObject], [putObject], [getList] and [putList] use Kotlin serialization, so [T] has to be -annotated as [Serializable], otherwise [SerializationException] will be thrown. +Note that `getObject`, `putObject`, `getList` and `putList` use Kotlin serialization, so `T` has to be +annotated as `Serializable`, otherwise `SerializationException` will be thrown. ## Nice! How do I get started? diff --git a/app/src/main/java/com/vojtkovszky/sharedpreferencesmanager/ExampleActivity.kt b/app/src/main/java/com/vojtkovszky/sharedpreferencesmanager/ExampleActivity.kt index ff7b1d8..531a821 100644 --- a/app/src/main/java/com/vojtkovszky/sharedpreferencesmanager/ExampleActivity.kt +++ b/app/src/main/java/com/vojtkovszky/sharedpreferencesmanager/ExampleActivity.kt @@ -24,12 +24,11 @@ class ExampleActivity : AppCompatActivity() { binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) + val sharedPreferences = applicationContext.getSharedPreferences("myCustomFileName", Context.MODE_PRIVATE) preferencesManager = SharedPreferencesManager( - context = applicationContext, - fileKey = "myCustomFileName", - operatingMode = Context.MODE_PRIVATE, - json = Json { isLenient = true }, - errorListener = { it.printStackTrace() } + sharedPreferences = sharedPreferences, + json = Json { isLenient = true }, + errorListener = { it.printStackTrace() } ) // preferencesManager = SharedPreferencesManager(this) // would work too, as other parameters are optional @@ -47,11 +46,11 @@ class ExampleActivity : AppCompatActivity() { binding.saveButton.setOnClickListener { // save doggo based on edit texts preferencesManager.setObject(KEY_DOGGO, - Dog( - name = binding.doggoName.text.toString(), - breed = binding.doggoBreed.text.toString(), - weightGrams = binding.doggoWeight.text.toString().toIntOrNull() ?: 0 - ) + Dog( + name = binding.doggoName.text.toString(), + breed = binding.doggoBreed.text.toString(), + weightGrams = binding.doggoWeight.text.toString().toIntOrNull() ?: 0 + ) ) // save checkbox state diff --git a/library/build.gradle b/library/build.gradle index d3cf901..ed6ebf8 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -21,7 +21,7 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 30 - versionName "1.1.1" + versionName "1.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/InMemorySharedPreferences.kt b/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/InMemorySharedPreferences.kt new file mode 100644 index 0000000..71923cf --- /dev/null +++ b/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/InMemorySharedPreferences.kt @@ -0,0 +1,199 @@ +@file:Suppress("unused") + +package com.vojtkovszky.sharedpreferencesmanager + +import android.content.SharedPreferences + +/** + * Implementation of [SharedPreferences] where data is persisted in memory only and omit using files. + * Useful if you need to run tests without mocking the context or if you simply don't need to persist + * data outside of application lifecycle. + */ +class InMemorySharedPreferences: SharedPreferences { + + // acting as repository + private var booleansMap: MutableMap = mutableMapOf() + private var floatsMap: MutableMap = mutableMapOf() + private var intsMap: MutableMap = mutableMapOf() + private var longMap: MutableMap = mutableMapOf() + private var stringsMap: MutableMap = mutableMapOf() + private var stringsSetMap: MutableMap?> = mutableMapOf() + + // change listeners + private val changeListeners: MutableList = mutableListOf() + + override fun getAll(): MutableMap { + val result: MutableMap = mutableMapOf() + result.putAll(booleansMap) + result.putAll(floatsMap) + result.putAll(intsMap) + result.putAll(longMap) + result.putAll(stringsMap) + result.putAll(stringsSetMap) + return result + } + + override fun getString(key: String?, value: String?): String? { + return stringsMap[key] ?: value + } + + override fun getStringSet(key: String?, value: MutableSet?): MutableSet { + return stringsSetMap[key] ?: mutableSetOf() + } + + override fun getInt(key: String?, value: Int): Int { + return intsMap[key] ?: value + } + + override fun getLong(key: String?, value: Long): Long { + return longMap[key] ?: value + } + + override fun getFloat(key: String?, value: Float): Float { + return floatsMap[key] ?: value + } + + override fun getBoolean(key: String?, value: Boolean): Boolean { + return booleansMap[key] ?: value + } + + override fun contains(key: String?): Boolean { + return all.containsKey(key) + } + + override fun edit(): SharedPreferences.Editor { + return LocalEditor() + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + listener?.let { + if (!changeListeners.contains(it)) { + changeListeners.add(it) + } + } + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { + listener?.let { + changeListeners.remove(it) + } + } + + /** + * Implementation of editor that doesn't rely on file storage. + */ + inner class LocalEditor: SharedPreferences.Editor { + + private val booleansMap: MutableMap = this@InMemorySharedPreferences.booleansMap + private val floatsMap: MutableMap = this@InMemorySharedPreferences.floatsMap + private val intsMap: MutableMap = this@InMemorySharedPreferences.intsMap + private val longMap: MutableMap = this@InMemorySharedPreferences.longMap + private val stringsMap: MutableMap = this@InMemorySharedPreferences.stringsMap + private val stringsSetMap: MutableMap?> = this@InMemorySharedPreferences.stringsSetMap + + private val changedKeys: MutableList = mutableListOf() + + override fun putString(key: String?, value: String?): SharedPreferences.Editor { + key?.let { + stringsMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun putStringSet(key: String?, value: MutableSet?): SharedPreferences.Editor { + key?.let { + stringsSetMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun putInt(key: String?, value: Int): SharedPreferences.Editor { + key?.let { + intsMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun putLong(key: String?, value: Long): SharedPreferences.Editor { + key?.let { + longMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun putFloat(key: String?, value: Float): SharedPreferences.Editor { + key?.let { + floatsMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun putBoolean(key: String?, value: Boolean): SharedPreferences.Editor { + key?.let { + booleansMap[it] = value + changedKeys.add(it) + } + return this + } + + override fun remove(key: String?): SharedPreferences.Editor { + key?.let { + booleansMap.remove(it) + floatsMap.remove(it) + intsMap.remove(it) + longMap.remove(it) + stringsMap.remove(it) + stringsSetMap.remove(it) + + changedKeys.add(it) + } + return this + } + + override fun clear(): SharedPreferences.Editor { + for (bool in booleansMap) { changedKeys.add(bool.key) } + for (float in floatsMap) { changedKeys.add(float.key) } + for (int in intsMap) { changedKeys.add(int.key) } + for (long in longMap) { changedKeys.add(long.key) } + for (string in stringsMap) { changedKeys.add(string.key) } + for (stringSet in stringsSetMap) { changedKeys.add(stringSet.key) } + + booleansMap.clear() + floatsMap.clear() + intsMap.clear() + longMap.clear() + stringsMap.clear() + stringsSetMap.clear() + + return this + } + + override fun commit(): Boolean { + apply() + return true + } + + override fun apply() { + this@InMemorySharedPreferences.booleansMap = this@LocalEditor.booleansMap + this@InMemorySharedPreferences.floatsMap = this@LocalEditor.floatsMap + this@InMemorySharedPreferences.intsMap = this@LocalEditor.intsMap + this@InMemorySharedPreferences.longMap = this@LocalEditor.longMap + this@InMemorySharedPreferences.stringsMap = this@LocalEditor.stringsMap + this@InMemorySharedPreferences.stringsSetMap = this@LocalEditor.stringsSetMap + + // distribute change listeners + for (listener in changeListeners) { + for (changedKey in changedKeys) { + listener.onSharedPreferenceChanged(this@InMemorySharedPreferences, changedKey) + } + } + changeListeners.clear() + } + + } +} \ No newline at end of file diff --git a/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/SharedPreferencesManager.kt b/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/SharedPreferencesManager.kt index baee120..a52687c 100644 --- a/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/SharedPreferencesManager.kt +++ b/library/src/main/kotlin/com/vojtkovszky/sharedpreferencesmanager/SharedPreferencesManager.kt @@ -1,4 +1,4 @@ -@file:Suppress("unused") +@file:Suppress("unused", "MemberVisibilityCanBePrivate") package com.vojtkovszky.sharedpreferencesmanager @@ -14,27 +14,18 @@ import kotlin.Exception * All put methods will automatically call [SharedPreferences.edit] concluded with * [SharedPreferences.apply]. * - * @param context Required to retrieve [SharedPreferences] within provided context. - * @param fileKey Desired preferences file. Defaults to [DEFAULT_FILE_KEY] - * @param operatingMode Operating mode of preferences file, defaults to [DEFAULT_MODE] - * @param json Provide your own [Json] instance. Defaults to [Json.Default] + * @param sharedPreferences provide your implementation of [SharedPreferences]. + * Usually it would be [Context.getSharedPreferences]. + * If persisting data in file is not desired, provide [InMemorySharedPreferences]. + * @param json Provide your own [Json] instance used for serialization of custom objects. Defaults to [Json.Default] * @param errorListener any exceptions thrown while parsing will be invoked using this listener */ class SharedPreferencesManager( - context: Context, - fileKey: String = DEFAULT_FILE_KEY, - operatingMode: Int = DEFAULT_MODE, + val sharedPreferences: SharedPreferences, val json: Json = Json, val errorListener: ((e: Exception) -> Unit)? = null ) { - companion object { - const val DEFAULT_FILE_KEY = "app_prefs" - const val DEFAULT_MODE = Context.MODE_PRIVATE - } - - private val sharedPreferences = context.applicationContext.getSharedPreferences(fileKey, operatingMode) - // region Boolean /** * Will return [SharedPreferences.getBoolean] @@ -171,7 +162,7 @@ class SharedPreferencesManager( /** * Will return [SharedPreferences.getString] */ - fun getString(key: String, defaultValue: String?): String? { + fun getString(key: String, defaultValue: String? = null): String? { return sharedPreferences.getString(key, defaultValue) ?: defaultValue } @@ -187,7 +178,7 @@ class SharedPreferencesManager( /** * Will return [SharedPreferences.getStringSet] */ - fun getStringSet(key: String, defaultValue: MutableSet?): MutableSet? { + fun getStringSet(key: String, defaultValue: MutableSet? = null): MutableSet? { return sharedPreferences.getStringSet(key, defaultValue) ?: defaultValue }