Skip to content

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Vojtkovszky committed Jul 27, 2021
2 parents d645b41 + 6dfd74b commit 21a3f29
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 36 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -41,8 +51,8 @@ fun getAll(): MutableMap<String, *>?
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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionName "1.1.1"
versionName "1.2.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Boolean> = mutableMapOf()
private var floatsMap: MutableMap<String, Float> = mutableMapOf()
private var intsMap: MutableMap<String, Int> = mutableMapOf()
private var longMap: MutableMap<String, Long> = mutableMapOf()
private var stringsMap: MutableMap<String, String?> = mutableMapOf()
private var stringsSetMap: MutableMap<String, MutableSet<String>?> = mutableMapOf()

// change listeners
private val changeListeners: MutableList<SharedPreferences.OnSharedPreferenceChangeListener> = mutableListOf()

override fun getAll(): MutableMap<String, *> {
val result: MutableMap<String, Any?> = 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<String>?): MutableSet<String> {
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<String, Boolean> = this@InMemorySharedPreferences.booleansMap
private val floatsMap: MutableMap<String, Float> = this@InMemorySharedPreferences.floatsMap
private val intsMap: MutableMap<String, Int> = this@InMemorySharedPreferences.intsMap
private val longMap: MutableMap<String, Long> = this@InMemorySharedPreferences.longMap
private val stringsMap: MutableMap<String, String?> = this@InMemorySharedPreferences.stringsMap
private val stringsSetMap: MutableMap<String, MutableSet<String>?> = this@InMemorySharedPreferences.stringsSetMap

private val changedKeys: MutableList<String> = 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<String>?): 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()
}

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@file:Suppress("unused")
@file:Suppress("unused", "MemberVisibilityCanBePrivate")

package com.vojtkovszky.sharedpreferencesmanager

Expand All @@ -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]
Expand Down Expand Up @@ -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
}

Expand All @@ -187,7 +178,7 @@ class SharedPreferencesManager(
/**
* Will return [SharedPreferences.getStringSet]
*/
fun getStringSet(key: String, defaultValue: MutableSet<String>?): MutableSet<String>? {
fun getStringSet(key: String, defaultValue: MutableSet<String>? = null): MutableSet<String>? {
return sharedPreferences.getStringSet(key, defaultValue) ?: defaultValue
}

Expand Down

0 comments on commit 21a3f29

Please sign in to comment.