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 9198c40..7fb635d 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 @@ -32,7 +32,10 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import android.app.Activity import android.content.Intent +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import java.util.UUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -162,9 +165,21 @@ class TransferFragment : Fragment() { } ).also { mfaisaHandler = it } - private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult - val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult + // Registered manually against the activity registry in onViewCreated rather than with a + // registerForActivityResult() field initializer: HomeActivity caches one TransferFragment + // instance (cachedTransferFragment) and re-shows it after a replace(). A field initializer + // runs only once, so once that instance was destroyed the launcher stayed unregistered and + // launching it threw. Keying off viewLifecycleOwner re-registers on every view creation. + private val qrLauncherKey = "transfer_qr_" + UUID.randomUUID() + private var qrLauncher: ActivityResultLauncher? = null + + private fun launchQrScanner() { + qrLauncher?.launch(Intent(requireContext(), QrScannerActivity::class.java)) + } + + private fun onQrScanned(result: ActivityResult) { + if (result.resultCode != Activity.RESULT_OK) return + val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return // BML card/gateway QR — hand off to dedicated payment screen val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw) @@ -173,7 +188,7 @@ class TransferFragment : Fragment() { it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" } (requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, fromCard?.accountNumber)) - return@registerForActivityResult + return } // M-Faisa merchant QR — content is just the numeric qrCodeId. Only attempt the lookup @@ -184,13 +199,13 @@ class TransferFragment : Fragment() { if (trimmedRaw.length in 8..16 && trimmedRaw.all { it.isDigit() } && app.mfaisaSessions.isNotEmpty()) { lookupMfaisaQrMerchant(trimmedRaw) - return@registerForActivityResult + return } val qr = PaymvQrParser.parse(raw) if (qr == null || qr.accountNumber == null) { Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show() - return@registerForActivityResult + return } // Cards can't pay PayMV QR — fall back to default account or clear selection @@ -287,6 +302,10 @@ class TransferFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + qrLauncher = requireActivity().activityResultRegistry.register( + qrLauncherKey, viewLifecycleOwner, ActivityResultContracts.StartActivityForResult() + ) { onQrScanned(it) } + setupFromDropdown() setupAccountLookup() @@ -340,7 +359,7 @@ class TransferFragment : Fragment() { } binding.btnScanQr.setOnClickListener { - qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java)) + launchQrScanner() } viewModel.connectivityErrors.observe(viewLifecycleOwner) { updateTransferButton() } @@ -369,7 +388,7 @@ class TransferFragment : Fragment() { arguments?.getString(ARG_BML_QR_URL)?.let { bmlHandler().lookupQrMerchant(it) } if (arguments?.getBoolean(ARG_AUTO_SCAN, false) == true) { - qrLauncher.launch(Intent(requireContext(), QrScannerActivity::class.java)) + launchQrScanner() } // Restore form state when view is recreated on the cached no-args instance @@ -2002,6 +2021,8 @@ class TransferFragment : Fragment() { bmlHandler = null mfaisaHandler?.clearState() mfaisaHandler = null + // Unregistered automatically with viewLifecycleOwner; drop the stale handle. + qrLauncher = null _binding = null }