Updated on 2026-08-14
This commit is contained in:
parent
3a96b203e0
commit
710002e992
5 changed files with 198 additions and 7 deletions
|
|
@ -25,18 +25,24 @@ dependencies {
|
|||
api(projects.domain.models)
|
||||
// endregion
|
||||
|
||||
// Project - Data
|
||||
// region Project - Data
|
||||
implementation(projects.data.common)
|
||||
// endregion
|
||||
|
||||
// region DI
|
||||
implementation(deps.hilt.core)
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
// region AndroidX libraries
|
||||
implementation(deps.androidx.datastore)
|
||||
// endregion
|
||||
|
||||
// region Other Dependencies
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
// endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.data.account.converter
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Container for converter factories related to accounts.
|
||||
*
|
||||
* @property accountsListCF factory for creating an account list converter
|
||||
* @property getWalletAccountsResponseCF factory for creating a wallet accounts response converter
|
||||
* @property cryptoPortfolioCF factory for creating a crypto portfolio converter
|
||||
*
|
||||
* @constructor Creates an instance of the container with injected factories.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AccountConverterFactoryContainer @Inject constructor(
|
||||
val accountsListCF: AccountListConverter.Factory,
|
||||
val getWalletAccountsResponseCF: GetWalletAccountsResponseConverter.Factory,
|
||||
val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
|
||||
)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.data.account.store
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
typealias AccountsResponseStore = DataStore<GetWalletAccountsResponse?>
|
||||
|
||||
/**
|
||||
* Factory class for creating and managing instances of [AccountsResponseStore].
|
||||
* This class is responsible for creating a [DataStore] for each unique [UserWalletId].
|
||||
*
|
||||
* @property context application context used to access the file system
|
||||
* @property moshi moshi instance for JSON serialization and deserialization
|
||||
* @property dispatchers coroutine dispatcher provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AccountsResponseStoreFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val adapter by lazy { moshi.adapter<GetWalletAccountsResponse?>() }
|
||||
|
||||
private val createdDataStores = ConcurrentHashMap<UserWalletId, AccountsResponseStore>()
|
||||
|
||||
/**
|
||||
* Creates or retrieves an [AccountsResponseStore] for the given [UserWalletId].
|
||||
*
|
||||
* @param userWalletId the unique identifier of the user's wallet
|
||||
*/
|
||||
fun create(userWalletId: UserWalletId): AccountsResponseStore {
|
||||
return createdDataStores.computeIfAbsent(userWalletId) {
|
||||
DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(defaultValue = null, adapter = adapter),
|
||||
produceFile = { context.dataStoreFile(fileName = "wallet_accounts_${userWalletId.stringValue}") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun getAllStores(): Map<UserWalletId, AccountsResponseStore> = createdDataStores.toMap()
|
||||
|
||||
@VisibleForTesting
|
||||
fun clearStores() {
|
||||
createdDataStores.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.data.account.store
|
||||
|
||||
import android.content.Context
|
||||
import com.google.common.truth.Truth
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AccountsResponseStoreFactoryTest {
|
||||
|
||||
private val context: Context = mockk()
|
||||
private val moshi: Moshi = Moshi.Builder().build()
|
||||
private val factory: AccountsResponseStoreFactory = AccountsResponseStoreFactory(
|
||||
context = context,
|
||||
moshi = moshi,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun setup() {
|
||||
clearMocks(context)
|
||||
factory.clearStores()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `creates new data store for unique userWalletId`() {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("011")
|
||||
val createdStore = factory.create(userWalletId = userWalletId)
|
||||
|
||||
// Actual
|
||||
val actual = factory.getAllStores()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).containsExactly(userWalletId, createdStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reuses existing data store for same userWalletId`() {
|
||||
val userWalletId = UserWalletId("011")
|
||||
|
||||
// Arrange (first creation)
|
||||
val firstStore = factory.create(userWalletId = userWalletId)
|
||||
|
||||
// Act (first creation)
|
||||
val actual1 = factory.getAllStores()
|
||||
|
||||
// Assert (first creation)
|
||||
Truth.assertThat(actual1).containsExactly(userWalletId, firstStore)
|
||||
|
||||
// Arrange (second creation)
|
||||
val secondStore = factory.create(userWalletId = userWalletId)
|
||||
|
||||
// Act (second creation)
|
||||
val actual2 = factory.getAllStores()
|
||||
|
||||
// Assert (second creation)
|
||||
Truth.assertThat(actual2).containsExactly(userWalletId, secondStore)
|
||||
Truth.assertThat(firstStore).isSameInstanceAs(secondStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `creates separate data stores for different userWalletIds`() {
|
||||
// Arrange (first creation)
|
||||
val firstWalletId = UserWalletId("011")
|
||||
val firstStore = factory.create(userWalletId = firstWalletId)
|
||||
|
||||
// Act (first creation)
|
||||
val actual1 = factory.getAllStores()
|
||||
|
||||
// Assert (first creation)
|
||||
Truth.assertThat(actual1).containsExactly(firstWalletId, firstStore)
|
||||
|
||||
// Arrange (second creation)
|
||||
val secondWalletId = UserWalletId("011")
|
||||
val secondStore = factory.create(userWalletId = secondWalletId)
|
||||
|
||||
// Act (second creation)
|
||||
val actual2 = factory.getAllStores()
|
||||
|
||||
// Assert (second creation)
|
||||
val expected = mapOf(firstWalletId to firstStore, secondWalletId to secondStore)
|
||||
Truth.assertThat(actual2).containsExactlyEntriesIn(expected)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue