NTP-21060 | Money manager - DB Query Events (#14515)

This commit is contained in:
Sanjay P
2025-01-15 17:58:54 +05:30
committed by GitHub
parent 7fbdd84bb9
commit 1ee9573645
12 changed files with 513 additions and 175 deletions

View File

@@ -12,6 +12,8 @@ import com.navi.moneymanager.common.dataprovider.data.addcategory.helper.AddCate
import com.navi.moneymanager.common.dataprovider.data.addcategory.helper.SimilarTransactionProviderHelper
import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper
import com.navi.moneymanager.common.dataprovider.domain.AddCategoryDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.utils.Constants
import com.navi.moneymanager.common.utils.calculateTimestamps
@@ -22,7 +24,7 @@ import com.navi.moneymanager.postonboard.monthlysummary.model.ChipsContainerData
import com.navi.moneymanager.postonboard.monthlysummary.model.SimilarTransactionBottomSheetData
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class AddCategoryDataProviderImpl
@@ -33,11 +35,27 @@ constructor(
private val similarTransactionProviderHelper: SimilarTransactionProviderHelper,
private val mmConfigResponseHelper: MMConfigResponseHelper,
) : AddCategoryDataProvider {
override suspend fun fetchAddCategoryBottomSheetData(transactionId: String) = flow {
override suspend fun fetchAddCategoryBottomSheetData(
transactionId: String
): Flow<AddCategoryBottomSheetData> = flow {
val transaction =
encryptedDatabase.get().transactionsDao().fetchTransaction(transactionId).first()
executeQueryFirst(
queryName = encryptedDatabase.get().transactionsDao()::fetchTransaction.name,
methodName = ::fetchAddCategoryBottomSheetData.name,
flow = encryptedDatabase.get().transactionsDao().fetchTransaction(transactionId),
)
val account =
encryptedDatabase.get().accountsDao().fetchAccount(transaction.linkedAccRef.orEmpty())
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAccount.name,
methodName = ::fetchAddCategoryBottomSheetData.name,
flow =
encryptedDatabase
.get()
.accountsDao()
.fetchAccount(transaction.linkedAccRef.orEmpty()),
)
val mmConfig = mmConfigResponseHelper.getMMConfig()
val hasOnlyOneSimilarTransaction =
if (transaction.counterPartyName.equals(Constants.UNKNOWN, ignoreCase = true)) {
@@ -45,15 +63,24 @@ constructor(
} else {
val (startDate, endDate) = calculateTimestamps()
val similarTransactionsCount =
encryptedDatabase
.get()
.transactionsDao()
.getTransactionCountByCounterPartyNameAndType(
counterPartyName = transaction.counterPartyName.orEmpty(),
transactionType = transaction.type.orEmpty(),
startDate,
endDate,
)
executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::
getTransactionCountByCounterPartyNameAndType
.name,
methodName = ::fetchAddCategoryBottomSheetData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.getTransactionCountByCounterPartyNameAndType(
counterPartyName = transaction.counterPartyName.orEmpty(),
transactionType = transaction.type.orEmpty(),
startDate,
endDate,
),
)
similarTransactionsCount == 1
}
AddCategoryEventTrackerImpl.addCategoryBottomSheetFetchDataCalled(
@@ -87,21 +114,39 @@ constructor(
transactionId: String,
categoryId: String,
transactionType: String,
) = flow {
): Flow<SimilarTransactionBottomSheetData> = flow {
val transaction =
encryptedDatabase.get().transactionsDao().fetchTransaction(transactionId).first()
executeQueryFirst(
queryName = encryptedDatabase.get().transactionsDao()::fetchTransaction.name,
methodName = ::fetchSimilarTransactionBottomSheetData.name,
flow = encryptedDatabase.get().transactionsDao().fetchTransaction(transactionId),
)
val (startDate, endDate) = calculateTimestamps()
val similarTransactions =
encryptedDatabase
.get()
.transactionsDao()
.fetchTransactionsByCounterPartyName(
transaction.counterPartyName.orEmpty(),
startDate,
endDate,
executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::
fetchTransactionsByCounterPartyName
.name,
methodName = ::fetchSimilarTransactionBottomSheetData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.fetchTransactionsByCounterPartyName(
transaction.counterPartyName.orEmpty(),
startDate,
endDate,
),
)
.filter { it.txnId != transaction.txnId && it.type == transactionType }
val accounts = encryptedDatabase.get().accountsDao().fetchAllAccounts().first()
val accounts =
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::fetchSimilarTransactionBottomSheetData.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
val mmConfig = mmConfigResponseHelper.getMMConfig()
AddCategoryEventTrackerImpl.similarTransactionsBottomSheetFetchDataCalled(
@@ -132,6 +177,15 @@ constructor(
}
override suspend fun updateTransactionEntity(transactionId: String, categoryId: String) {
encryptedDatabase.get().transactionsDao().updateTransactionEntity(transactionId, categoryId)
executeQuery(
queryName = encryptedDatabase.get().transactionsDao()::updateTransactionEntity.name,
methodName = ::updateTransactionEntity.name,
query = {
encryptedDatabase
.get()
.transactionsDao()
.updateTransactionEntity(transactionId, categoryId)
},
)
}
}

View File

@@ -8,13 +8,20 @@
package com.navi.moneymanager.common.dataprovider.data.bankaccounts
import com.navi.moneymanager.common.dataprovider.domain.BankAccountsDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.model.database.AccountOverview
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
class BankAccountsDataProviderImpl
@Inject
constructor(private val encryptedDatabase: Lazy<MMEncryptedDatabase>) : BankAccountsDataProvider {
override suspend fun getBankAccounts() =
encryptedDatabase.get().accountsDao().fetchAllAccounts()
override suspend fun getBankAccounts(): Flow<List<AccountOverview>> =
executeQueryFlow(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::getBankAccounts.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
}

View File

@@ -24,6 +24,8 @@ import com.navi.moneymanager.common.dataprovider.data.transaction.helper.Transac
import com.navi.moneymanager.common.dataprovider.domain.DashboardDataProvider
import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.COMMA
import com.navi.moneymanager.common.dataprovider.utils.DataProviderConstants.LAST_UPDATED_REFRESH_SCHEDULER_DELAY
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.dataprovider.utils.getLastTwelveMonths
import com.navi.moneymanager.common.dataprovider.utils.toCapitalizedWords
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
@@ -105,8 +107,13 @@ constructor(
) : DashboardDataProvider {
@OptIn(FlowPreview::class)
private fun getTransactionCountFlow(): Flow<Int> {
return encryptedDatabase.get().transactionsDao().getTransactionCount().debounce(200)
private suspend fun getTransactionCountFlow(): Flow<Int> {
return executeQueryFlow(
queryName = encryptedDatabase.get().transactionsDao()::getTransactionCount.name,
methodName = ::getTransactionCountFlow.name,
flow = encryptedDatabase.get().transactionsDao().getTransactionCount(),
)
.debounce(200)
}
private suspend fun isFirstMonthSyncedFlow() =
@@ -162,7 +169,13 @@ constructor(
Flow<Pair<BankAccountsState, IsCurrentMonthSynced>> {
return combineWithFlatMapLatest(
flow1 = syncStatusFlow(),
flow2 = encryptedDatabase.get().accountsDao().fetchAllAccounts().distinctUntilChanged(),
flow2 =
executeQueryFlow(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::getBankSectionStateFlow.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
.distinctUntilChanged(),
combineTransform = { (isCurrentMonthSynced, isTotalDataSynced), accounts ->
Triple(isCurrentMonthSynced, isTotalDataSynced, accounts)
},
@@ -225,10 +238,11 @@ constructor(
override suspend fun updateBankSectionRefreshingText(
refreshing: Boolean
): Flow<List<BankAccountFooterSection>> =
encryptedDatabase
.get()
.accountsDao()
.fetchAllAccounts()
executeQueryFlow(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::updateBankSectionRefreshingText.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
.distinctUntilChanged()
.flatMapLatest { accounts ->
flow {
@@ -272,12 +286,20 @@ constructor(
)
val categorySummaryList =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummary(
month = params.month ?: dateComponents.second,
year = params.year ?: dateComponents.third,
executeQueryFlow(
queryName =
encryptedDatabase.get().transactionsDao()::
getCategorizedTransactionSummary
.name,
methodName = ::getSpendCategorizationSectionStateFlow.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummary(
month = params.month ?: dateComponents.second,
year = params.year ?: dateComponents.third,
),
)
.firstOrNull() ?: emptyList()
@@ -377,16 +399,22 @@ constructor(
val bankTransactionDataList =
accountList.map { account ->
val transactions: List<TransactionSummaryData> =
encryptedDatabase
.get()
.transactionsDao()
.fetchTransactionsForBank(
linkedAccRef = account.linkedAccRef,
startDate = startDate,
endDate = endDate,
limit = RECENT_TRANSACTION_COUNT,
)
.first()
executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::fetchTransactionsForBank
.name,
methodName = ::getRecentTransactionsSectionState.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.fetchTransactionsForBank(
linkedAccRef = account.linkedAccRef,
startDate = startDate,
endDate = endDate,
limit = RECENT_TRANSACTION_COUNT,
),
)
val filteredTransactions: List<TransactionSummaryData> =
filterTransactions(transactions, currentMonthTag, isTotalSyncCompleted)
@@ -411,6 +439,7 @@ constructor(
)
createLoadedTransactions(bankTransactionDataList)
}
bankTransactionDataList.flatMap { it.transactions }.isEmpty() -> {
DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionLoading(
isFirstMonthSyncCompleted = isCurrentMonthSyncCompleted,
@@ -419,6 +448,7 @@ constructor(
)
createLoadingTransactions()
}
else -> {
DataProviderEventTrackerImpl.dashboardDpRecentTransactionSectionLoaded(
isFirstMonthSyncCompleted = isCurrentMonthSyncCompleted,
@@ -445,6 +475,7 @@ constructor(
formattedMonthTag == currentMonthTag.value
}
}
else -> {
transactions
.firstOrNull { it.hasValidMonthYear() }

View File

@@ -10,6 +10,8 @@ package com.navi.moneymanager.common.dataprovider.data.remote
import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper
import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider
import com.navi.moneymanager.common.dataprovider.domain.LocalDataSyncManager
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.helper.AccountsDataHelper
import com.navi.moneymanager.common.helper.TransactionsDataHelper
@@ -33,14 +35,26 @@ constructor(
private val mmConfigResponseHelper: Lazy<MMConfigResponseHelper>,
) : LocalDataSyncManager {
override suspend fun insertAccounts(accounts: List<AccountData>) {
encryptedDatabase
.get()
.accountsDao()
.deleteAccountsNotIn(accountsDataHelper.getAccountsEntity(accounts))
encryptedDatabase
.get()
.accountsDao()
.insertAllAccounts(accountsDataHelper.getAccountsEntity(accounts))
executeQuery(
queryName = encryptedDatabase.get().accountsDao()::deleteAccountsNotIn.name,
methodName = ::insertAccounts.name,
query = {
encryptedDatabase
.get()
.accountsDao()
.deleteAccountsNotIn(accountsDataHelper.getAccountsEntity(accounts))
},
)
executeQuery(
queryName = encryptedDatabase.get().accountsDao()::insertAllAccounts.name,
methodName = ::insertAccounts.name,
query = {
encryptedDatabase
.get()
.accountsDao()
.insertAllAccounts(accountsDataHelper.getAccountsEntity(accounts))
},
)
}
override suspend fun insertTransactions(
@@ -48,12 +62,18 @@ constructor(
isCurrentMonthData: Boolean,
) {
val mmConfig = mmConfigResponseHelper.get().getMMConfig()
encryptedDatabase
.get()
.transactionsDao()
.insertAllTransactions(
transactionsDataHelper.getTransactionsEntity(transactions, mmConfig)
)
executeQuery(
queryName = encryptedDatabase.get().transactionsDao()::insertAllTransactions.name,
methodName = ::insertTransactions.name,
query = {
encryptedDatabase
.get()
.transactionsDao()
.insertAllTransactions(
transactionsDataHelper.getTransactionsEntity(transactions, mmConfig)
)
},
)
if (!isCurrentMonthData && transactions.isNotEmpty())
updatedLastSyncTimestamp(transactions.last().mobileTimestamp)
@@ -87,7 +107,12 @@ constructor(
override suspend fun getTransactionCount(): Int {
val (startDate, endDate) = calculateTimestamps()
return encryptedDatabase.get().transactionsDao().getTransactionsCount(startDate, endDate)
return executeQueryFirst(
queryName = encryptedDatabase.get().transactionsDao()::getTransactionsCount.name,
methodName = ::getTransactionCount.name,
flow =
encryptedDatabase.get().transactionsDao().getTransactionsCount(startDate, endDate),
)
}
override suspend fun updateNewTransactionCount(newTransaction: Int) {
@@ -95,6 +120,11 @@ constructor(
}
override suspend fun getLatestTransactionTimestamp(): Long {
return encryptedDatabase.get().transactionsDao().getLatestTransactionTimestamp()
return executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::getLatestTransactionTimestamp.name,
methodName = ::getLatestTransactionTimestamp.name,
flow = encryptedDatabase.get().transactionsDao().getLatestTransactionTimestamp(),
)
}
}

View File

@@ -18,6 +18,8 @@ import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.Spend
import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.TotalSpendSectionDataHelper
import com.navi.moneymanager.common.dataprovider.data.transaction.helper.TransactionProviderHelper
import com.navi.moneymanager.common.dataprovider.domain.CategoryDetailsLocalDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.dataprovider.utils.getLastTwelveMonths
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.helper.convertMonthStringToPair
@@ -95,10 +97,16 @@ constructor(
selectedCategory: String,
): Flow<CategoryDetailsScreenData> {
val (startDate, endDate) = calculateTimestamps()
return encryptedDatabase
.get()
.transactionsDao()
.fetchCategorisedTransactions(selectedCategory, startDate, endDate)
return executeQueryFlow(
queryName =
encryptedDatabase.get().transactionsDao()::fetchCategorisedTransactions.name,
methodName = ::getCategoryDetailsScreenData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.fetchCategorisedTransactions(selectedCategory, startDate, endDate),
)
.map { transactions ->
Timber.tag("money_manager")
.d(
@@ -198,7 +206,12 @@ constructor(
override suspend fun getBankSelectionBottomSheetData(
selectedBankReferenceIds: Set<String>
): BankSelectionBottomSheetData {
val bankAccounts = encryptedDatabase.get().accountsDao().fetchAllAccounts().first()
val bankAccounts =
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::getBankSelectionBottomSheetData.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
return bankSelectionBottomSheetDataHelper.getBankSelectionBottomSheetData(
bankAccounts,
selectedBankReferenceIds,

View File

@@ -19,6 +19,8 @@ import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.Spend
import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.SpendAnalysisCategoriesSectionHelper
import com.navi.moneymanager.common.dataprovider.data.spendanalysis.helper.TotalSpendSectionDataHelper
import com.navi.moneymanager.common.dataprovider.domain.SpendAnalysisLocalDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.dataprovider.utils.getLastTwelveMonths
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.helper.convertMonthStringToPair
@@ -61,10 +63,15 @@ constructor(
selectedBankReferenceIds: Set<String>?,
): Flow<SpendAnalysisScreenData> {
val (startDate, endDate) = calculateTimestamps()
return encryptedDatabase
.get()
.transactionsDao()
.fetchAllTransactions(startDate, endDate)
return executeQueryFlow(
queryName = encryptedDatabase.get().transactionsDao()::fetchAllTransactions.name,
methodName = ::getSpendAnalysisScreenData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.fetchAllTransactions(startDate, endDate),
)
.map { transactions ->
Timber.tag("money_manager")
.d(
@@ -72,7 +79,11 @@ constructor(
)
val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis())
val availableBanks =
encryptedDatabase.get().accountsDao().fetchAllAccounts().first()
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::getSpendAnalysisScreenData.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
val updatedSelectedBankReferenceIds =
selectedBankReferenceIds
?: availableBanks
@@ -90,15 +101,22 @@ constructor(
}
val mmConfig = mmConfigResponseHelper.getMMConfig()
val categories =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummaryForSelectedBanks(
month = month ?: dateComponents.second,
year = year ?: dateComponents.third,
linkedAccRef = updatedSelectedBankReferenceIds,
)
.first()
executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::
getCategorizedTransactionSummaryForSelectedBanks
.name,
methodName = ::getSpendAnalysisScreenData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummaryForSelectedBanks(
month = month ?: dateComponents.second,
year = year ?: dateComponents.third,
linkedAccRef = updatedSelectedBankReferenceIds,
),
)
DataProviderEventTrackerImpl.spendAnalysisDpData(
month = month.toString(),
year = year.toString(),
@@ -178,20 +196,32 @@ constructor(
selectedBankReferenceIds: Set<String>?,
): OtherCategoriesBottomSheetData {
val dateComponents = getDayMonthAndYearFromTimestamp(System.currentTimeMillis())
val availableBanks = encryptedDatabase.get().accountsDao().fetchAllAccounts().first()
val availableBanks =
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = ::getOtherCategoriesBottomSheetData.name,
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
val updatedSelectedBankReferenceIds =
selectedBankReferenceIds ?: availableBanks.map { it.linkedAccRef }.toSet()
val mmConfig = mmConfigResponseHelper.getMMConfig()
val categories =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummaryForSelectedBanks(
month = month ?: dateComponents.second,
year = year ?: dateComponents.third,
updatedSelectedBankReferenceIds,
)
.first()
executeQueryFirst(
queryName =
encryptedDatabase.get().transactionsDao()::
getCategorizedTransactionSummaryForSelectedBanks
.name,
methodName = ::getOtherCategoriesBottomSheetData.name,
flow =
encryptedDatabase
.get()
.transactionsDao()
.getCategorizedTransactionSummaryForSelectedBanks(
month = month ?: dateComponents.second,
year = year ?: dateComponents.third,
updatedSelectedBankReferenceIds,
),
)
return otherCategoriesBottomSheetDataHelper.getOtherCategoriesBottomSheetData(
categories,
mmConfig,

View File

@@ -18,6 +18,8 @@ import com.navi.moneymanager.R
import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl
import com.navi.moneymanager.common.dataprovider.data.dashboard.helper.MMConfigResponseHelper
import com.navi.moneymanager.common.dataprovider.domain.TransactionDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.dataprovider.utils.getTransactionDate
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.illustration.model.IllustrationSource
@@ -56,69 +58,90 @@ constructor(
@ApplicationContext private val context: Context,
) : TransactionDataProvider {
override suspend fun getTransactionDetailScreenData(transactionId: String) = flow {
encryptedDatabase.get().transactionsDao().fetchTransactionDetails(transactionId).collect {
val mmConfig = mmConfigResponseHelper.getMMConfig()
val accountInfo =
encryptedDatabase.get().accountsDao().fetchAccount(it.linkedAccRef.orEmpty())
DataProviderEventTrackerImpl.transactionDetailsDpData(accountInfo.isNotNull())
emit(
TransactionDetailsScreenData(
topNavBar = NavBarData(actionLabel = HELP_CTA_TEXT),
transactionInfo =
TransactionInfo(
amount = it.txnAmount.orZero().formatToInrWithTwoDecimals(),
transactionOutcome = getTransactionOutcome(it.type),
transactionDate = getTransactionDate(it.txnTimestamp),
paymentDirectionIcon = getPaymentDirectionIcon(it.type),
),
categoryInfo = getCategoryInfo(it, mmConfig),
transactionSummary =
TransactionSummary(
counterpartyInfo =
CounterpartyInfo(
paymentNarrative = getCounterPartyPaymentNarrative(it.type),
counterPartyName = it.counterPartyName.orEmpty(),
),
accountHolderInfo =
AccountHolderInfo(
paymentNarrative = getAccountHolderPaymentNarrative(it.type),
bankDetails =
BankAccountDisplayInfo(
bankAccountDisplayText =
"${accountInfo.bankCode} - ${
executeQueryFlow(
queryName = encryptedDatabase.get().transactionsDao()::fetchTransactionDetails.name,
methodName = "getTransactionDetailScreenData",
flow =
encryptedDatabase.get().transactionsDao().fetchTransactionDetails(transactionId),
)
.collect {
val mmConfig = mmConfigResponseHelper.getMMConfig()
val accountInfo =
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAccount.name,
methodName = "getTransactionDetailScreenData",
flow =
encryptedDatabase
.get()
.accountsDao()
.fetchAccount(it.linkedAccRef.orEmpty()),
)
DataProviderEventTrackerImpl.transactionDetailsDpData(accountInfo.isNotNull())
emit(
TransactionDetailsScreenData(
topNavBar = NavBarData(actionLabel = HELP_CTA_TEXT),
transactionInfo =
TransactionInfo(
amount = it.txnAmount.orZero().formatToInrWithTwoDecimals(),
transactionOutcome = getTransactionOutcome(it.type),
transactionDate = getTransactionDate(it.txnTimestamp),
paymentDirectionIcon = getPaymentDirectionIcon(it.type),
),
categoryInfo = getCategoryInfo(it, mmConfig),
transactionSummary =
TransactionSummary(
counterpartyInfo =
CounterpartyInfo(
paymentNarrative = getCounterPartyPaymentNarrative(it.type),
counterPartyName = it.counterPartyName.orEmpty(),
),
accountHolderInfo =
AccountHolderInfo(
paymentNarrative =
getAccountHolderPaymentNarrative(it.type),
bankDetails =
BankAccountDisplayInfo(
bankAccountDisplayText =
"${accountInfo.bankCode} - ${
accountInfo.maskedAccNumber?.takeLast(
4
)
}",
bankIcon =
IllustrationSource.Remote(
url = accountInfo.bankIconUrl.orEmpty(),
placeholder = ALL_BANK_ICON_SMALL,
),
bankIcon =
IllustrationSource.Remote(
url = accountInfo.bankIconUrl.orEmpty(),
placeholder = ALL_BANK_ICON_SMALL,
),
),
),
transactionMetadata =
listOf(
TransactionMetadataItem(
title = context.getString(R.string.mode),
subtitle = it.mode.orEmpty(),
),
TransactionMetadataItem(
title = context.getString(R.string.narration),
subtitle = it.narration.orEmpty(),
),
),
transactionMetadata =
listOf(
TransactionMetadataItem(
title = context.getString(R.string.mode),
subtitle = it.mode.orEmpty(),
),
TransactionMetadataItem(
title = context.getString(R.string.narration),
subtitle = it.narration.orEmpty(),
),
),
),
),
)
)
)
}
}
}
override suspend fun getTransactionCategoryInfo(transactionId: String) = flow {
encryptedDatabase.get().transactionsDao().fetchTransactionDetails(transactionId).collect {
val mmConfig = mmConfigResponseHelper.getMMConfig()
emit(getCategoryInfo(it, mmConfig))
}
executeQueryFlow(
queryName = encryptedDatabase.get().transactionsDao()::fetchTransactionDetails.name,
methodName = "getTransactionCategoryInfo",
flow =
encryptedDatabase.get().transactionsDao().fetchTransactionDetails(transactionId),
)
.collect {
val mmConfig = mmConfigResponseHelper.getMMConfig()
emit(getCategoryInfo(it, mmConfig))
}
}
private fun getCategoryInfo(

View File

@@ -17,6 +17,9 @@ import com.navi.moneymanager.common.analytics.DataProviderEventTrackerImpl
import com.navi.moneymanager.common.dataprovider.data.transaction.helper.FilterOptionDataHelper
import com.navi.moneymanager.common.dataprovider.data.transaction.helper.TransactionProviderHelper
import com.navi.moneymanager.common.dataprovider.domain.TransactionHistoryDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFirst
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryFlow
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQueryPagingSource
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.illustration.model.IllustrationSource
import com.navi.moneymanager.common.illustration.repository.ImageRepository.Companion.COMMON_EMPTY_PLACEHOLDER
@@ -116,18 +119,39 @@ constructor(
pagingSourceFactory = {
when {
isQueryEmptyAndNoFiltersApplied(updatedQuery, appliedFilters) ->
dao.fetchAllTransactionPaged(startDate, endDate)
executeQueryPagingSource(
methodName = ::getTransactionPagingSource.name,
queryName = dao::fetchAllTransactionPaged.name,
query = { dao.fetchAllTransactionPaged(startDate, endDate) },
)
noFiltersApplied(appliedFilters) ->
dao.fetchAllQueryTransactionPaged("%$updatedQuery%", startDate, endDate)
executeQueryPagingSource(
methodName = ::getTransactionPagingSource.name,
queryName = dao::fetchAllQueryTransactionPaged.name,
query = {
dao.fetchAllQueryTransactionPaged(
"%$updatedQuery%",
startDate,
endDate,
)
},
)
else ->
dao.fetchAllQueryAndFiltersTransactionPaged(
input = "%$updatedQuery%",
appliedMonthAndYearFilter = appliedMonthAndYearFilter,
appliedCategoriesFilter = appliedCategoriesFilter,
appliedTypeFilter = appliedTypeFilter,
appliedBankFilter = appliedBankFilter,
startDate = startDate,
endDate = endDate,
executeQueryPagingSource(
methodName = ::getTransactionPagingSource.name,
queryName = dao::fetchAllQueryAndFiltersTransactionPaged.name,
query = {
dao.fetchAllQueryAndFiltersTransactionPaged(
input = "%$updatedQuery%",
appliedMonthAndYearFilter = appliedMonthAndYearFilter,
appliedCategoriesFilter = appliedCategoriesFilter,
appliedTypeFilter = appliedTypeFilter,
appliedBankFilter = appliedBankFilter,
startDate = startDate,
endDate = endDate,
)
},
)
}
},
@@ -136,10 +160,15 @@ constructor(
return pager.flow.map { pagingData ->
pagingData.map { transaction ->
val accountInfo =
encryptedDatabase
.get()
.accountsDao()
.fetchAccount(transaction.linkedAccRef.orEmpty())
executeQueryFirst(
queryName = encryptedDatabase.get().accountsDao()::fetchAccount.name,
methodName = ::getTransactionPagingSource.name,
flow =
encryptedDatabase
.get()
.accountsDao()
.fetchAccount(transaction.linkedAccRef.orEmpty()),
)
transactionProviderHelper.createTransaction(
transaction.copy(bankIconUrl = accountInfo.bankIconUrl)
)
@@ -156,9 +185,14 @@ constructor(
appliedFilters.isEmpty()
override fun getInitFilterData() = flow {
encryptedDatabase.get().accountsDao().fetchAllAccounts().collect {
val filterItemData = filterOptionDataHelper.getFilterOptionsData(it)
emit(filterItemData)
}
executeQueryFlow(
queryName = encryptedDatabase.get().accountsDao()::fetchAllAccounts.name,
methodName = "getInitFilterData",
flow = encryptedDatabase.get().accountsDao().fetchAllAccounts(),
)
.collect {
val filterItemData = filterOptionDataHelper.getFilterOptionsData(it)
emit(filterItemData)
}
}
}

View File

@@ -0,0 +1,106 @@
/*
*
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.moneymanager.common.dataprovider.utils
import androidx.paging.PagingSource
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.transform
import timber.log.Timber
class MMQueryExecutor @Inject constructor() {
companion object {
// Executes a query and tracks its execution time
suspend fun executeQuery(queryName: String, methodName: String, query: suspend () -> Unit) {
Timber.d(
"QueryExecution executeQuery Executing query: methodName - $methodName, queryName - $queryName"
)
val startTime = System.currentTimeMillis()
query.invoke()
val endTime = System.currentTimeMillis()
logAndTrackQueryTime(queryName, methodName, startTime, endTime, "executeQuery")
}
// Executes a Flow query and returns the first result, while logging execution time
suspend fun <T> executeQueryFirst(queryName: String, methodName: String, flow: Flow<T>): T {
Timber.d(
"QueryExecution executeQueryFirst Executing query (first value): methodName - $methodName, queryName - $queryName"
)
val startTime = System.currentTimeMillis()
val result = flow.first()
val endTime = System.currentTimeMillis()
logAndTrackQueryTime(queryName, methodName, startTime, endTime, "executeQueryFirst")
return result
}
// Executes a Flow query and logs execution time for the first emission
suspend fun <T> executeQueryFlow(
queryName: String,
methodName: String,
flow: Flow<T>,
): Flow<T> {
var isFirstElementEmitted = false
val startTime = System.currentTimeMillis()
return flow.transform { value ->
if (!isFirstElementEmitted) {
isFirstElementEmitted = true
val endTime = System.currentTimeMillis()
Timber.d(
"QueryExecution executeQueryFlow Executing query (flow): methodName - $methodName, queryName - $queryName"
)
logAndTrackQueryTime(
queryName,
methodName,
startTime,
endTime,
"executeQueryFlow",
)
}
emit(value) // Emit the first value of the flow*/
}
}
fun <Key : Any, Value : Any> executeQueryPagingSource(
queryName: String,
methodName: String,
query: () -> PagingSource<Key, Value>,
): PagingSource<Key, Value> {
Timber.d(
"QueryExecution executeQueryPagingSource Executing query (paging source): methodName - $methodName, queryName - $queryName"
)
val startTime = System.currentTimeMillis()
val result = query.invoke()
val endTime = System.currentTimeMillis()
logAndTrackQueryTime(
queryName,
methodName,
startTime,
endTime,
"executeQueryPagingSource",
)
return result
}
// Helper function to log and track query execution time
private fun logAndTrackQueryTime(
queryName: String,
methodName: String,
startTime: Long,
endTime: Long,
method: String,
) {
val elapsedTime = endTime - startTime
Timber.d(
"QueryExecution $method Query executed in $elapsedTime ms: methodName - $methodName, queryName - $queryName"
)
}
}
}

View File

@@ -1,6 +1,6 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * Copyright © 2024-2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
@@ -26,7 +26,7 @@ interface AccountsDao {
"FROM $ACCOUNT_TABLE " +
"WHERE linkedAccRef = :linkedAccountReference"
)
suspend fun fetchAccount(linkedAccountReference: String): AccountOverview
fun fetchAccount(linkedAccountReference: String): Flow<AccountOverview>
@Query(
"SELECT linkedAccRef, accountHolderName, maskedAccNumber, bankName, bankCode, bankIconUrl, currentBalance, updatedAt " +

View File

@@ -185,7 +185,7 @@ interface TransactionsDao {
counterPartyName: String,
startDate: Long,
endDate: Long,
): List<TransactionSummaryData>
): Flow<List<TransactionSummaryData>>
@Query(
" SELECT finalCategory, COUNT(txnId) AS numberOfTransactions, SUM(txnAmount) AS totalAmount " +
@@ -215,7 +215,7 @@ interface TransactionsDao {
@Query(
"SELECT COUNT(*) FROM $TRANSACTION_TABLE WHERE txnTimestamp BETWEEN :startDate AND :endDate "
)
suspend fun getTransactionsCount(startDate: Long, endDate: Long): Int
fun getTransactionsCount(startDate: Long, endDate: Long): Flow<Int>
@Query("UPDATE $TRANSACTION_TABLE SET finalCategory = :categoryId WHERE txnId = :transactionId")
suspend fun updateTransactionEntity(transactionId: String, categoryId: String)
@@ -229,8 +229,8 @@ interface TransactionsDao {
transactionType: String,
startDate: Long,
endDate: Long,
): Int
): Flow<Int>
@Query("SELECT MAX(txnTimestamp) FROM $TRANSACTION_TABLE")
suspend fun getLatestTransactionTimestamp(): Long
fun getLatestTransactionTimestamp(): Flow<Long>
}

View File

@@ -13,6 +13,7 @@ import com.navi.base.utils.orZero
import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider
import com.navi.moneymanager.common.dataprovider.domain.DashboardDataProvider
import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider
import com.navi.moneymanager.common.dataprovider.utils.MMQueryExecutor.Companion.executeQuery
import com.navi.moneymanager.common.db.database.MMEncryptedDatabase
import com.navi.moneymanager.common.model.SelectedMonth
import com.navi.moneymanager.common.navigation.utils.MMScreen
@@ -40,10 +41,19 @@ constructor(
remoteDataProvider.fetchMMConfigResponse(screenName = MMScreen.DASHBOARD.screen)
return networkResponse.data?.let {
encryptedDatabase
.get()
.transactionsDao()
.deleteTransactionsOlderThan(it.timestampConfig?.twelveMonthsOldTimestamp.orZero())
executeQuery(
queryName =
encryptedDatabase.get().transactionsDao()::deleteTransactionsOlderThan.name,
methodName = ::fetchAndSaveConfigResponse.name,
query = {
encryptedDatabase
.get()
.transactionsDao()
.deleteTransactionsOlderThan(
it.timestampConfig?.twelveMonthsOldTimestamp.orZero()
)
},
)
dashboardDataProvider.saveMMConfigToDB(it)
dashboardDataProvider.getMMConfig()
} ?: run { null }