Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-27 17:08:13 +03:00
parent c1561a366c
commit c6fc0d49ba
10 changed files with 135 additions and 33 deletions

View file

@ -1,15 +1,11 @@
package com.tangem.datasource.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.tangem.datasource.local.datastore.JsonSharedPreferencesDataStore
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.quote.DefaultQuotesStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.quote.model.StoredQuote
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@ -19,13 +15,9 @@ internal object QuotesStoreModule {
@Provides
@Singleton
fun provideQuotesStore(@ApplicationContext context: Context, @NetworkMoshi moshi: Moshi): QuotesStore {
fun provideQuotesStore(): QuotesStore {
return DefaultQuotesStore(
dataStore = JsonSharedPreferencesDataStore(
preferencesName = "quotes",
context = context,
adapter = moshi.adapter(StoredQuote::class.java),
),
dataStore = RuntimeDataStore(),
)
}
}

View file

@ -17,6 +17,14 @@ internal class FileDataStore<Value : Any>(
) : StringKeyDataStore<Value> {
private val writeTrigger = Trigger()
override suspend fun isEmpty(): Boolean {
val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`")
Timber.e(e)
throw e
}
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
override fun get(key: String): Flow<Value> {
return writeTrigger

View file

@ -11,6 +11,10 @@ internal class RuntimeDataStore<Data : Any> : StringKeyDataStore<Data> {
store.tryEmit(value = null)
}
override suspend fun isEmpty(): Boolean = store.firstOrNull().isNullOrEmpty()
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
override fun get(key: String): Flow<Data> {
return store.mapNotNull { value ->
value?.get(key)

View file

@ -26,6 +26,14 @@ internal abstract class SharedPreferencesDataStore<Value : Any>(
abstract fun storeByKey(key: String, value: Value)
override suspend fun isEmpty(): Boolean {
return sharedPreferences.all.isEmpty()
}
override suspend fun contains(key: String): Boolean {
return sharedPreferences.contains(key)
}
override fun get(key: String): Flow<Value> {
return writeTrigger
.mapNotNull { getInternal(key) }

View file

@ -2,23 +2,94 @@ package com.tangem.datasource.local.datastore.core
import kotlinx.coroutines.flow.Flow
/**
* Represents a generic key-value data store.
*
* @param Key The type of the keys used to identify values in the store.
* @param Value The type of the values stored.
*/
internal interface DataStore<Key : Any, Value : Any> {
/**
* Checks if the data store is empty.
*
* @return `true` if the data store has no entries, otherwise `false`.
*/
suspend fun isEmpty(): Boolean
/**
* Checks if the data store contains an entry with the specified key.
*
* @param key The key to check for presence in the store.
* @return `true` if the key is present, otherwise `false`.
*/
suspend fun contains(key: Key): Boolean
/**
* Retrieves a value updates associated with the given key, as a flow.
*
* @param key The key to look up in the store.
* @return A flow emitting the value associated with the given key.
*/
fun get(key: Key): Flow<Value>
/**
* Retrieves all values updates from the data store, as a flow.
*
* @return A flow emitting a list of all values in the store.
*/
fun getAll(): Flow<List<Value>>
/**
* Retrieves a value associated with the given key synchronously.
*
* If the key does not exist, this method returns `null`.
*
* @param key The key to look up in the store.
* @return The value associated with the key, or `null` if not present.
*/
suspend fun getSyncOrNull(key: Key): Value?
/**
* Retrieves all values from the data store synchronously.
*
* If the store is empty, this method returns `null`.
*
* @return A list of all values in the store, or `null` if empty.
*/
suspend fun getAllSyncOrNull(): List<Value>?
/**
* Stores a value in the data store associated with the given key.
*
* @param key The key to associate with the value.
* @param value The value to store.
*/
suspend fun store(key: Key, value: Value)
/**
* Stores multiple values in the data store with their associated keys.
*
* @param values A map of keys to values to store.
*/
suspend fun store(values: Map<Key, Value>)
/**
* Removes a value associated with the given key from the data store.
*
* @param key The key of the value to remove.
*/
suspend fun remove(key: Key)
/**
* Removes values associated with the given keys from the data store.
*
* @param keys Keys of values to remove.
*/
suspend fun remove(keys: Collection<Key>)
/**
* Clears all entries from the data store.
*/
suspend fun clear()
}

View file

@ -13,7 +13,7 @@ internal abstract class KeylessDataStoreDecorator<Value : Any>(
open suspend fun store(item: Value) = store(key, item)
open suspend fun isEmpty() = getSyncOrNull() == null
override suspend fun isEmpty(): Boolean = getSyncOrNull() == null
private companion object {
const val DEFAULT_STRING_KEY = "key"

View file

@ -8,6 +8,10 @@ internal abstract class StringKeyDataStoreDecorator<Key : Any, Value : Any>(
abstract fun provideStringKey(key: Key): String
override suspend fun isEmpty(): Boolean = wrappedDataStore.isEmpty()
override suspend fun contains(key: Key): Boolean = wrappedDataStore.contains(provideStringKey(key))
override fun get(key: Key): Flow<Value> {
return wrappedDataStore.get(provideStringKey(key))
}

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.combine
internal class DefaultQuotesStore(
@ -12,16 +13,24 @@ internal class DefaultQuotesStore(
) : QuotesStore {
override fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>> {
val flows = currenciesIds.mapNotNull { currencyId ->
dataStore.get(currencyId.rawCurrencyId ?: return@mapNotNull null)
}
return channelFlow {
val flows = currenciesIds.mapNotNull { currencyId ->
currencyId.rawCurrencyId?.let(dataStore::get)
}
return combine(flows) { quotes -> quotes.toSet() }
if (dataStore.isEmpty() || flows.isEmpty()) {
send(emptySet())
}
combine(flows) { quotes -> quotes.toSet() }.collect(::send)
}
}
override suspend fun store(response: QuotesResponse) {
response.quotes.forEach { (rawCurrencyId, quote) ->
dataStore.store(rawCurrencyId, StoredQuote(rawCurrencyId, quote))
val quotes = response.quotes.mapValues { (id, quote) ->
StoredQuote(id, quote)
}
dataStore.store(quotes)
}
}