From c4d3c1efd469bd1f9d1ff8686409cd1d9ce04a61 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Thu, 28 May 2026 00:14:11 +0500 Subject: [PATCH] better network error handling, fix crash when no network in transaction history page --- .../ui/home/AccountHistoryFragment.kt | 13 +++- .../ui/home/ContactPickerSheetFragment.kt | 2 +- .../sar/basedbank/ui/home/ContactsFragment.kt | 2 +- .../basedbank/ui/home/FinancingFragment.kt | 2 +- .../sh/sar/basedbank/ui/home/HomeActivity.kt | 2 +- .../sar/basedbank/ui/home/TransferFragment.kt | 20 ++++-- .../ui/home/TransferHistoryFragment.kt | 70 +++++++++++++------ 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt index 3b841bc..2d4232d 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountHistoryFragment.kt @@ -150,8 +150,15 @@ class AccountHistoryFragment : Fragment() { pendingIconUrls.clear() firstPageDone = false fetcher = HistoryFetcher(account) - adapter.setTransactions(emptyList()) - binding.emptyView.visibility = View.GONE + // Restore cache immediately so data stays visible while refreshing + val cached = TransactionCache.load(requireContext(), account.accountNumber) + if (cached.isNotEmpty()) { + allTransactions.addAll(cached) + filterAndDisplay() + } else { + adapter.setTransactions(emptyList()) + binding.emptyView.visibility = View.GONE + } loadNextPage() } @@ -179,6 +186,7 @@ class AccountHistoryFragment : Fragment() { } isLoading = false + if (_binding == null) return@launch if (!firstPageDone) { firstPageDone = true @@ -191,6 +199,7 @@ class AccountHistoryFragment : Fragment() { if (allTransactions.isEmpty()) binding.emptyView.visibility = View.VISIBLE return@launch } + (activity as? HomeActivity)?.hideConnectivityBanner() if (transactions.isNotEmpty()) { val existingIds = allTransactions.map { it.id }.toHashSet() diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt index 49175c9..1fe9811 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactPickerSheetFragment.kt @@ -147,7 +147,7 @@ class ContactPickerSheetFragment : BottomSheetDialogFragment() { viewModel.accounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } viewModel.hideAmounts.observe(viewLifecycleOwner) { pagerAdapter.rebuildAll() } - (activity as? HomeActivity)?.loadAllContacts() + (activity as? HomeActivity)?.triggerRefresh() } private fun attachMediator(pages: List) { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt index 97298f1..eb9887a 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/ContactsFragment.kt @@ -137,7 +137,7 @@ class ContactsFragment : Fragment() { binding.swipeRefresh.setOnRefreshListener { contactsRefreshing = true - (activity as? HomeActivity)?.loadAllContacts() + (activity as? HomeActivity)?.triggerRefresh() } viewModel.contactCategories.observe(viewLifecycleOwner) { cats -> diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt index 6883bb0..7e32808 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/FinancingFragment.kt @@ -47,7 +47,7 @@ class FinancingFragment : Fragment() { binding.swipeRefresh.setOnRefreshListener { financingRefreshing = true - (activity as? HomeActivity)?.triggerRefreshFinancing() + (activity as? HomeActivity)?.triggerRefresh() } viewModel.accounts.observe(viewLifecycleOwner) { rebuildAdapter() } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 538c590..c2191d1 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -554,7 +554,7 @@ fun applyNavLabelVisibility() { binding.connectivityBanner.visibility = View.VISIBLE } - private fun hideConnectivityBanner() { + fun hideConnectivityBanner() { binding.connectivityBanner.visibility = View.GONE } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index c43632e..5d0642d 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -859,7 +859,11 @@ class TransferFragment : Fragment() { activity.triggerRefresh() activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar)) } else if (!ok) { - Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show() + if (msg == "CONNECTIVITY") { + (activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet)) + } else { + Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show() + } } } } @@ -1069,7 +1073,7 @@ class TransferFragment : Fragment() { Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null) } } catch (e: Exception) { - Triple(false, e.message ?: "Transfer failed", null) + Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null) } } @@ -1122,7 +1126,7 @@ class TransferFragment : Fragment() { // Step 1: initiate val initiated = try { BmlTransferClient().initiateTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, bank) - } catch (e: Exception) { return Triple(false, e.message ?: "Initiation failed", null) } + } catch (e: Exception) { return Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Initiation failed"), null) } if (!initiated) return Triple(false, "Failed to initiate transfer — check your session", null) @@ -1154,7 +1158,7 @@ class TransferFragment : Fragment() { Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null) } } catch (e: Exception) { - Triple(false, e.message ?: "Transfer failed", null) + Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null) } } @@ -1396,7 +1400,7 @@ class TransferFragment : Fragment() { Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null as TransferReceiptData?) } } catch (e: Exception) { - Triple(false, e.message ?: "Transfer failed", null as TransferReceiptData?) + Triple(false, if (e is java.io.IOException) "CONNECTIVITY" else (e.message ?: "Transfer failed"), null as TransferReceiptData?) } } (activity as? HomeActivity)?.setRefreshing(false) @@ -1410,7 +1414,11 @@ class TransferFragment : Fragment() { activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar)) } else { binding.btnTransfer.isEnabled = true - binding.tilBmlOtp.error = msg + if (msg == "CONNECTIVITY") { + (activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet)) + } else { + binding.tilBmlOtp.error = msg + } } } } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferHistoryFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferHistoryFragment.kt index 47b7158..82bac28 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferHistoryFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferHistoryFragment.kt @@ -30,6 +30,7 @@ import sh.sar.basedbank.api.fahipay.FahipayHistoryClient import sh.sar.basedbank.api.models.BankAccount import sh.sar.basedbank.api.mib.MibContactsClient import sh.sar.basedbank.api.mib.MibHistoryClient +import sh.sar.basedbank.api.models.BankServerException import sh.sar.basedbank.api.models.BankTransaction import sh.sar.basedbank.api.mib.TransactionCache import sh.sar.basedbank.databinding.FragmentTransferHistoryBinding @@ -161,8 +162,15 @@ class TransferHistoryFragment : Fragment() { val accounts = accountStates.map { it.account } accountStates.clear() accounts.forEach { accountStates.add(AccountState(it)) } - adapter.setTransactions(emptyList()) - binding.emptyView.visibility = View.GONE + // Restore cache immediately so data stays visible while refreshing + val cached = TransactionCache.load(requireContext(), "transfer") + if (cached.isNotEmpty()) { + allTransactions.addAll(cached) + filterAndDisplay() + } else { + adapter.setTransactions(emptyList()) + binding.emptyView.visibility = View.GONE + } loadNextPages() } @@ -178,6 +186,14 @@ class TransferHistoryFragment : Fragment() { val app = requireActivity().application as BasedBankApp lifecycleScope.launch { + val bannerMsg = java.util.concurrent.atomic.AtomicReference(null) + fun trackError(e: Exception) { + when { + e is java.io.IOException -> bannerMsg.compareAndSet(null, "IO") + e is BankServerException -> bannerMsg.compareAndSet(null, "SERVER:${e.bankName}") + } + } + val newTransactions = withContext(Dispatchers.IO) { val results = mutableListOf() @@ -215,7 +231,7 @@ class TransferHistoryFragment : Fragment() { list } } - } catch (_: Exception) { emptyList() } + } catch (e: Exception) { trackError(e); emptyList() } } }.awaitAll().flatten()) @@ -233,7 +249,7 @@ class TransferHistoryFragment : Fragment() { if (total > 0) state.fahipayTotal = total state.fahipayNextStart += list.size results.addAll(list) - } catch (_: Exception) {} + } catch (e: Exception) { trackError(e) } } // MIB accounts: serialized per profile, protected by mutex to prevent session race @@ -242,23 +258,25 @@ class TransferHistoryFragment : Fragment() { val session = app.mibSessions[loginId] ?: continue for ((profileId, states) in loginStates.groupBy { it.account.profileId }) { app.mibMutex.withLock { - val profiles = app.mibProfilesMap[loginId] ?: emptyList() - val profile = profiles.firstOrNull { it.profileId == profileId } - if (profile != null) app.mibFlowFor(loginId).switchProfile(session, profile) - for (state in states) { - try { - val (list, total) = MibHistoryClient().fetchHistory( - session = session, - accountNo = state.account.accountNumber, - accountDisplayName = state.account.accountBriefName, - start = state.mibNextStart, - pageSize = pageSize - ) - if (total > 0) state.mibTotalCount = total - state.mibNextStart += list.size.coerceAtLeast(pageSize) - results.addAll(list) - } catch (_: Exception) {} - } + try { + val profiles = app.mibProfilesMap[loginId] ?: emptyList() + val profile = profiles.firstOrNull { it.profileId == profileId } + if (profile != null) app.mibFlowFor(loginId).switchProfile(session, profile) + for (state in states) { + try { + val (list, total) = MibHistoryClient().fetchHistory( + session = session, + accountNo = state.account.accountNumber, + accountDisplayName = state.account.accountBriefName, + start = state.mibNextStart, + pageSize = pageSize + ) + if (total > 0) state.mibTotalCount = total + state.mibNextStart += list.size.coerceAtLeast(pageSize) + results.addAll(list) + } catch (e: Exception) { trackError(e) } + } + } catch (e: Exception) { trackError(e) } } } } @@ -267,6 +285,16 @@ class TransferHistoryFragment : Fragment() { } isLoading = false + if (_binding == null) return@launch + + val raw = bannerMsg.get() + when { + raw == null -> (activity as? HomeActivity)?.hideConnectivityBanner() + raw == "IO" -> (activity as? HomeActivity)?.showConnectivityBanner(getString(R.string.connectivity_no_internet)) + raw.startsWith("SERVER:") -> (activity as? HomeActivity)?.showConnectivityBanner( + getString(R.string.connectivity_server_error, raw.removePrefix("SERVER:")) + ) + } if (!firstBatchDone) { firstBatchDone = true