MicroUtils/repos/common/src/androidMain/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt

197 lines
6.4 KiB
Kotlin
Raw Normal View History

2020-11-10 09:43:15 +00:00
package dev.inmo.micro_utils.repos.keyvalue
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
2022-07-25 07:31:15 +00:00
import dev.inmo.micro_utils.pagination.*
2020-11-10 09:43:15 +00:00
import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
2022-06-04 08:42:16 +00:00
import dev.inmo.micro_utils.repos.KeyValueRepo
2023-07-23 07:47:20 +00:00
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
import kotlinx.coroutines.flow.*
2020-11-10 09:43:15 +00:00
private val cache = HashMap<String, KeyValueStore<*>>()
fun <T : Any> Context.keyValueStore(
name: String = "default",
cacheValues: Boolean = false
2022-06-04 08:42:16 +00:00
): KeyValueRepo<String, T> {
2022-04-27 09:15:03 +00:00
@Suppress("UNCHECKED_CAST")
2020-11-10 09:43:15 +00:00
return cache.getOrPut(name) {
2022-09-14 16:47:57 +00:00
KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues)
2020-11-10 09:43:15 +00:00
} as KeyValueStore<T>
}
class KeyValueStore<T : Any> internal constructor (
c: Context,
preferencesName: String,
useCache: Boolean = false
2022-06-04 08:42:16 +00:00
) : SharedPreferences.OnSharedPreferenceChangeListener, KeyValueRepo<String, T> {
2020-11-10 09:43:15 +00:00
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
private val cachedData = if (useCache) {
mutableMapOf<String, Any>()
} else {
null
}
private val onNewValueChannel = MutableSharedFlow<Pair<String, T>>()
private val _onValueRemovedFlow = MutableSharedFlow<String>()
2020-11-10 09:43:15 +00:00
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asSharedFlow()
override val onValueRemoved: Flow<String> = _onValueRemovedFlow.asSharedFlow()
2020-11-10 09:43:15 +00:00
init {
cachedData ?.let {
2021-05-06 11:52:53 +00:00
for ((key, value) in sharedPreferences.all) {
if (value != null) {
cachedData[key] = value
2020-11-10 09:43:15 +00:00
}
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
}
override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) {
val value = sp.all[key]
cachedData ?: return
if (value != null) {
cachedData[key] = value
} else {
cachedData.remove(key)
}
}
override suspend fun get(k: String): T? {
2022-04-27 09:15:03 +00:00
@Suppress("UNCHECKED_CAST")
2020-11-10 09:43:15 +00:00
return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<T> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.values.paginate(
resultPagination
).let {
2022-07-25 07:31:15 +00:00
it.changeResultsUnchecked(
2022-04-27 09:15:03 +00:00
it.results.map {
@Suppress("UNCHECKED_CAST")
it as T
2022-07-25 07:31:15 +00:00
}.let { if (reversed) it.reversed() else it }
2020-11-10 09:43:15 +00:00
)
}
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.keys.paginate(
resultPagination
).let {
2022-07-25 07:31:15 +00:00
it.changeResultsUnchecked(
it.results.let { if (reversed) it.reversed() else it }
2020-11-10 09:43:15 +00:00
)
}
}
override suspend fun keys(v: T, pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate(
resultPagination
).let {
2022-07-25 07:31:15 +00:00
it.changeResultsUnchecked(
it.results.let { if (reversed) it.reversed() else it }
)
}
}
2020-11-10 09:43:15 +00:00
override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key)
2023-03-10 12:37:48 +00:00
override suspend fun getAll(): Map<String, T> {
val resultMap = mutableMapOf<String, T>()
2023-03-10 12:43:43 +00:00
for ((k, v) in sharedPreferences.all) {
2023-03-10 12:37:48 +00:00
@Suppress("UNCHECKED_CAST")
2023-03-10 12:43:43 +00:00
resultMap[k] = (v as? T) ?: continue
2023-03-10 12:37:48 +00:00
}
return resultMap.toMap()
}
2020-11-10 09:43:15 +00:00
override suspend fun count(): Long = sharedPreferences.all.size.toLong()
override suspend fun set(toSet: Map<String, T>) {
sharedPreferences.edit {
2021-05-06 11:52:53 +00:00
for ((k, v) in toSet) {
2020-11-10 09:43:15 +00:00
when(v) {
is Int -> putInt(k, v)
is Long -> putLong(k, v)
is Float -> putFloat(k, v)
is String -> putString(k, v)
is Boolean -> putBoolean(k, v)
is Set<*> -> putStringSet(k, v.map { (it as? String) ?: it.toString() }.toSet())
else -> error(
"Currently supported only primitive types and set for SharedPreferences KeyValue repos"
)
}
}
}
2021-05-06 11:52:53 +00:00
for ((k, v) in toSet) {
onNewValueChannel.emit(k to v)
2020-11-10 09:43:15 +00:00
}
}
override suspend fun unset(toUnset: List<String>) {
sharedPreferences.edit {
2021-05-06 11:52:53 +00:00
for (item in toUnset) {
remove(item)
}
}
for (it in toUnset) {
_onValueRemovedFlow.emit(it)
2020-11-10 09:43:15 +00:00
}
}
override suspend fun unsetWithValues(toUnset: List<T>) {
val keysToRemove = sharedPreferences.all.mapNotNull { if (it.value in toUnset) it.key else null }
sharedPreferences.edit {
keysToRemove.map {
remove(it)
}
}
keysToRemove.forEach {
_onValueRemovedFlow.emit(it)
}
2020-11-10 09:43:15 +00:00
}
2022-09-14 16:47:57 +00:00
2023-07-23 07:47:20 +00:00
override suspend fun clear() {
val keys = mutableSetOf<String>()
doWithPagination(maxPagePagination()) {
keys(it).also {
keys.addAll(it.results)
}.nextPageIfNotEmpty()
}
val success = sharedPreferences.edit().apply {
clear()
}.commit()
if (success) {
keys.forEach {
_onValueRemovedFlow.emit(it)
}
}
}
2022-09-14 16:47:57 +00:00
companion object {
operator fun <T : Any> invoke(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
}
2020-11-10 09:43:15 +00:00
}
2022-05-16 11:34:34 +00:00
inline fun <T : Any> SharedPreferencesKeyValueRepo(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
2022-09-14 16:47:57 +00:00
typealias KeyValueSPRepo<T> = KeyValueStore<T>