From 4100a3c116c9b553477c58177d54dd5b73328edb Mon Sep 17 00:00:00 2001 From: Marinov Date: Fri, 6 Mar 2026 16:19:47 +0200 Subject: [PATCH 1/3] [MS-1068] Move setGraph before result handlers to allow reactive navigation when restoring state --- .../FaceCaptureControllerFragment.kt | 26 +++++++++---------- .../ExternalCredentialControllerFragment.kt | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt index 53f0895e8b..1068f48bc9 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt @@ -48,6 +48,19 @@ internal class FaceCaptureControllerFragment : Fragment(R.layout.fragment_face_c super.onViewCreated(view, savedInstanceState) Simber.i("FaceCaptureControllerFragment started", tag = ORCHESTRATION) + internalNavController + ?.navInflater + ?.inflate(R.navigation.graph_face_capture_internal) + ?.also { + it.setStartDestination( + if (viewModel.shouldShowInstructionsScreen()) { + R.id.facePreparationFragment + } else { + R.id.faceLiveFeedbackFragment + }, + ) + }?.let { internalNavController?.setGraph(it, null) } + findNavController().handleResult( this, R.id.faceCaptureControllerFragment, @@ -117,19 +130,6 @@ internal class FaceCaptureControllerFragment : Fragment(R.layout.fragment_face_c else -> findNavController().popBackStack() } } - - internalNavController - ?.navInflater - ?.inflate(R.navigation.graph_face_capture_internal) - ?.also { - it.setStartDestination( - if (viewModel.shouldShowInstructionsScreen()) { - R.id.facePreparationFragment - } else { - R.id.faceLiveFeedbackFragment - }, - ) - }?.let { internalNavController?.setGraph(it, null) } } private fun initFaceBioSdk() { diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialControllerFragment.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialControllerFragment.kt index 79a667b186..c6b254955b 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialControllerFragment.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/controller/ExternalCredentialControllerFragment.kt @@ -42,6 +42,8 @@ internal class ExternalCredentialControllerFragment : Fragment(R.layout.fragment viewModel.init(params) + internalNavController?.setGraph(R.navigation.graph_external_credential_internal) + findNavController().handleResult( this, R.id.externalCredentialControllerFragment, @@ -57,7 +59,6 @@ internal class ExternalCredentialControllerFragment : Fragment(R.layout.fragment ) } } - internalNavController?.setGraph(R.navigation.graph_external_credential_internal) initObservers() initListeners() From d1f839291cd04c847d7c1b9c005e8167b7bc4880 Mon Sep 17 00:00:00 2001 From: Marinov Date: Fri, 6 Mar 2026 16:21:00 +0200 Subject: [PATCH 2/3] [MS-1068] Set a guard against allowing navigation when currentDestination is null (graph not set) --- .../uibase/navigation/NavigationResultExt.kt | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt b/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt index f0658571c7..0d4b5386ed 100644 --- a/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt +++ b/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt @@ -173,31 +173,25 @@ private fun NavController.navigateIfPossible( } /** - * Only one navigation request needs to be processed from the fragment. This method checks if the - * no other navigation is scheduled in the [NavController]. It does so by checking whether the - * class name in the [NavController.currentDestination] is null or equals to the current fragment - * name. + * Checks whether the [NavController] is in a state that allows navigation from [currentFragment]. + * This prevents duplicate navigation requests when a fragment triggers navigation more than once. * - * - On the app startup, the [NavController.currentDestination] is null, since there were no - * navigation requests. - * - When the first navigation request to the target 'A' happens, then the field 'className' in the - * [NavController.currentDestination] becomes 'A'. - * - When the [currentFragment] 'A' wants to navigate to the destination 'B', this method checks if - * the current value of the [NavController.currentDestination] is still 'A' (it was set to 'A' - * during the previous navigation request). - * - If the name of the [currentFragment] is different to the 'className' in the - * [NavController.currentDestination], it means that the the current fragment has a navigation - * request scheduled already, and the navigation cannot be executed. + * - If [NavController.currentDestination] is null (no graph set), navigation is not possible. + * - When the [currentFragment] 'A' wants to navigate to destination 'B', this method checks if + * the 'className' in [NavController.currentDestination] still matches 'A'. + * - If it doesn't match, another navigation request has already been processed and navigation + * is rejected to prevent double-navigation. * * @param currentFragment - currently displayed fragment in the [NavController] - * @return true if the class name of the [currentFragment] is equal to the 'className' field in the - * [NavController.currentDestination], or if the [NavController.currentDestination] is null. false - * otherwise. + * @return true if [NavController.currentDestination] is set and its class name matches + * [currentFragment], or if [NavController.currentDestination] is not a [FragmentNavigator.Destination]. + * false if [currentFragment] is null or [NavController.currentDestination] is null. */ @ExcludedFromGeneratedTestCoverageReports("There is no reasonable way to test this") private fun NavController.canNavigate(currentFragment: Fragment?): Boolean { + val currentDest = currentDestination ?: return false val fragmentName = currentFragment?.let { it::class.java.name } - val targetClassName = (currentDestination as? FragmentNavigator.Destination)?.className + val targetClassName = (currentDest as? FragmentNavigator.Destination)?.className return currentFragment != null && (targetClassName == null || targetClassName == fragmentName) } From ac58e268f64cc1dac010ccdfbae9ca26653dc426 Mon Sep 17 00:00:00 2001 From: Marinov Date: Fri, 6 Mar 2026 16:29:19 +0200 Subject: [PATCH 3/3] [MS-1068] Simplify canNavigate() logic --- .../infra/uibase/navigation/NavigationResultExt.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt b/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt index 0d4b5386ed..5b4671a97b 100644 --- a/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt +++ b/infra/ui-base/src/main/java/com/simprints/infra/uibase/navigation/NavigationResultExt.kt @@ -185,14 +185,15 @@ private fun NavController.navigateIfPossible( * @param currentFragment - currently displayed fragment in the [NavController] * @return true if [NavController.currentDestination] is set and its class name matches * [currentFragment], or if [NavController.currentDestination] is not a [FragmentNavigator.Destination]. - * false if [currentFragment] is null or [NavController.currentDestination] is null. + * false if [currentFragment] is null, [NavController.currentDestination] is null, or the + * destination class name does not match [currentFragment] (indicating a navigation already occurred). */ @ExcludedFromGeneratedTestCoverageReports("There is no reasonable way to test this") private fun NavController.canNavigate(currentFragment: Fragment?): Boolean { + currentFragment ?: return false val currentDest = currentDestination ?: return false - val fragmentName = currentFragment?.let { it::class.java.name } val targetClassName = (currentDest as? FragmentNavigator.Destination)?.className - return currentFragment != null && (targetClassName == null || targetClassName == fragmentName) + return targetClassName == null || targetClassName == currentFragment::class.java.name } /**