Skip to content

Conversation

@jaypanchal-13
Copy link
Contributor

Crash Description

Ticket -> https://dimagi.atlassian.net/browse/CCCT-1619

      Fatal Exception: java.lang.RuntimeException: Unable to resume activity {org.commcare.dalvik/org.commcare.activities.connect.PersonalIdActivity}: java.lang.IllegalArgumentException: Navigation action/destination org.commcare.dalvik:id/action_personalid_biometric_config_to_personalid_message cannot be found from the current destination Destination(org.commcare.dalvik:id/personalid_message_display) label=fragment_personalid_message
   at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4907)
   at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4940)
   at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
   at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
   at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:178)
   at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:99)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2393)
   at android.os.Handler.dispatchMessage(Handler.java:106)
   at android.os.Looper.loopOnce(Looper.java:201)
   at android.os.Looper.loop(Looper.java:288)
   at android.app.ActivityThread.main(ActivityThread.java:8061)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:703)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)

RCA

Crashlytics reports show a IllegalArgumentException when trying to navigate from PersonalIdBiometricConfigFragment to PersonalIdMessageFragment while the app is already on PersonalIdMessageFragment. This happens because the Navigation component cannot find the action from the current destination (personalid_message_display) to itself

Solution

Adding a check against the current destination ensures we don’t attempt redundant navigation and prevents this kind of crash

Safety Assurance

Safety story

Automated test coverage

QA Plan

Labels and Review

  • Do we need to enhance the manual QA test coverage ? If yes, the "QA Note" label is set correctly
  • Does the PR introduce any major changes worth communicating ? If yes, the "Release Note" label is set and a "Release Note" is specified in PR description.
  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

📝 Walkthrough

Walkthrough

Refactors navigation in PersonalIdBiometricConfigFragment.navigateToMessageDisplay to use a local NavController with guards. It retrieves the NavController, checks for a non-null currentDestination, and only navigates to PersonalIdMessageDisplay if the current destination is different. NavDirections creation remains unchanged. This avoids redundant navigation or re-navigation when already on the target fragment and prevents potential NPEs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User
  participant Frag as PersonalIdBiometricConfigFragment
  participant Nav as NavController
  participant Dest as CurrentDestination
  participant Msg as PersonalIdMessageDisplay

  User->>Frag: Trigger navigateToMessageDisplay()
  Frag->>Nav: obtain NavController
  Frag->>Nav: getCurrentDestination()
  Nav-->>Frag: Destination (nullable)

  alt currentDestination is null
    Frag->>Nav: navigate(navDirections)  %% optional behavior depending on implementation
    note right of Frag: Guard prevents NPE by checking null before comparing IDs
  else currentDestination not null
    Frag->>Frag: compare Dest.id vs MessageDisplay.id
    alt Dest != MessageDisplay
      Frag->>Nav: navigate(navDirections)
      Nav->>Msg: Display
      note over Nav,Msg: Navigate to PersonalIdMessageDisplay
    else Dest == MessageDisplay
      Frag->>Frag: do nothing
      note right of Frag: Skip re-navigation
    end
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Added safe navigation #3045 — Introduces centralized safe navigation (navigateSafely) with destination checks, closely aligned with this PR’s guarded NavController.navigate change.

Suggested reviewers

  • OrangeAndGreen
  • shubham1g5
  • Jignesh-dimagi

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and succinctly describes the main change — a fix for the navigation crash to PersonalIdMessageFragment — and aligns with the changeset which adds a guard against redundant navigation; it is concise, on-topic, and clear for a reviewer scanning history.
Description Check ✅ Passed The description includes a clear crash report (stack trace), the RCA, the linked ticket (CCCT-1619), and a concise explanation of the implemented fix, which satisfies the Technical Summary/Product Description intent. However, the Safety Assurance subsections (Safety story, Automated test coverage, QA Plan) are present but contain no details and the PR lacks explicit Risk/QA/Release Note labeling.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch CCCT-1691-crash-on-navigateToMessageDisplay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (1)

352-364: Use NavHostFragment.findNavController(this) and restrict navigation to the source destination

File: app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (lines ~352–364) — Replace the current NavController lookup and condition so we avoid NPE when binding is null and only navigate when the current destination is the source fragment.

-        NavController navController = Navigation.findNavController(binding.getRoot());
-
-        // Only navigate if we’re not already on PersonalIdMessageFragment
-        if (navController.getCurrentDestination() != null && navController.getCurrentDestination().getId() != R.id.personalid_message_display) {
+        NavController navController = NavHostFragment.findNavController(this);
+        // Only navigate if the current destination is the source of this action
+        if (navController.getCurrentDestination() != null
+                && navController.getCurrentDestination().getId() == R.id.personalid_biometric_config) {
             navController.navigate(navDirections);
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a29e137 and 9ea00e8.

📒 Files selected for processing (1)
  • app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: shubham1g5
PR: dimagi/commcare-android#0
File: :0-0
Timestamp: 2025-05-08T11:08:18.530Z
Learning: PR #3048 "Phase 4 Connect PR" introduces a substantial feature called "Connect" to the CommCare Android app, which includes messaging, job management, delivery tracking, payment processing, authentication flows, and learning modules. It follows a modern architecture using Navigation Components with three navigation graphs, segregated business logic in Manager classes, and proper database persistence.
📚 Learning: 2025-05-09T10:57:41.073Z
Learnt from: Jignesh-dimagi
PR: dimagi/commcare-android#3093
File: app/res/navigation/nav_graph_connect_messaging.xml:41-45
Timestamp: 2025-05-09T10:57:41.073Z
Learning: In the CommCare Android codebase, the navigation graph for Connect messaging (`nav_graph_connect_messaging.xml`) intentionally uses `channel_id` as the argument name in the connectMessageFragment, despite using `channelId` in other parts of the same navigation graph. This naming difference is by design in the refactored code.

Applied to files:

  • app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Lint Code Base
🔇 Additional comments (1)
app/src/org/commcare/fragments/personalId/PersonalIdBiometricConfigFragment.java (1)

14-14: LGTM: appropriate NavController import.

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 You were able to reproduce this bug?

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 You were able to reproduce this bug?

@Jignesh-dimagi No, performing RCA added solution

Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaypanchal-13 This change will potentially cause a fragment transition to get ignored i.e. if the code is calling to show a particular message, it will never get shown to the user. As such the change is not right. Think the issue is instead a result of poor navigation or back navigation handling in some part of code and think the RCA here is incomplete. Think it would be critical to reproduce this issue for us to consider current solution here but given the low frequency of this crash, I think we should just deprioritize it if we are not able to easily repro this.

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 This change will potentially cause a fragment transition to get ignored i.e. if the code is calling to show a particular message, it will never get shown to the user. As such the change is not right. Think the issue is instead a result of poor navigation or back navigation handling in some part of code and think the RCA here is incomplete. Think it would be critical to reproduce this issue for us to consider current solution here but given the low frequency of this crash, I think we should just deprioritize it if we are not able to easily repro this.

@shubham1g5 To support that point I have tested mulitple scenarious with mutilple message. Futher navigateToMessageDisplay() is solely created to navigate to personalid_message_display screen and as per condition applied it will not apply navigation only when current screen is personalid_message_display in this case it will cancel navigation. So I think it will not affect transition. And yes if we are concen with message not been displayed. That case also not possible because as I added condition check, there will be no message which is shown twice in open dialog with different message. Still if you want we can discard this ticket as it is not reproducible.

@shubham1g5
Copy link
Contributor

Futher navigateToMessageDisplay() is solely created to navigate to personalid_message_display screen and as per condition applied it will not apply navigation only when current screen is personalid_message_display in this case it will cancel navigation

@jaypanchal-13 While this feels fine for message, I think introducing these kind of checks is a bad practice for overall health of code as I am pretty sure we will need to introduce similar checks for other fragment transitions as well and not just messages. If we except this solution here we will have to accept doing these checks for other fragment transitions which will decrease our overall code health in terms of managing these.

In our experience with PersonalID launch, These errors are always a result of a something amiss in our code with respect to navigation stack management. We have gone back and forth to adding these checks and later realising that the actual solution was correcting the nav stack is some part of the code which was causing these errors.

I also see in the crashes linked, that this always seem to happen when there is no biometric hardware present on the device, have you been able to test that workflow ?
Screenshot 2025-09-15 at 10 28 47 AM

@jaypanchal-13
Copy link
Contributor Author

Futher navigateToMessageDisplay() is solely created to navigate to personalid_message_display screen and as per condition applied it will not apply navigation only when current screen is personalid_message_display in this case it will cancel navigation

@jaypanchal-13 While this feels fine for message, I think introducing these kind of checks is a bad practice for overall health of code as I am pretty sure we will need to introduce similar checks for other fragment transitions as well and not just messages. If we except this solution here we will have to accept doing these checks for other fragment transitions which will decrease our overall code health in terms of managing these.

In our experience with PersonalID launch, These errors are always a result of a something amiss in our code with respect to navigation stack management. We have gone back and forth to adding these checks and later realising that the actual solution was correcting the nav stack is some part of the code which was causing these errors.

I also see in the crashes linked, that this always seem to happen when there is no biometric hardware present on the device, have you been able to test that workflow ? Screenshot 2025-09-15 at 10 28 47 AM

@shubham1g5 Yes I verified and tested this before but was not able to reproduce it. And I tested now I am getting some crash printed in warring logs when biometric button is clicked and when there is some issue with biometric. I think this might be the case. What do you think on it?

java.lang.Exception: Mode 15 encountered unexpected status 1 at org.commcare.utils.BiometricsHelper.checkStatus(BiometricsHelper.java:181) at org.commcare.utils.BiometricsHelper.checkFingerprintStatus(BiometricsHelper.java:54) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.onFingerprintButtonClicked(PersonalIdBiometricConfigFragment.java:192) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.lambda$onCreateView$0(PersonalIdBiometricConfigFragment.java:76) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.$r8$lambda$Rw9nj2U3dnIrBY4yRk8Cw4fw-L4(PersonalIdBiometricConfigFragment.java:0) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment$$ExternalSyntheticLambda1.onClick(R8$$SyntheticClass:0) at android.view.View.performClick(View.java:7792) at android.widget.TextView.performClick(TextView.java:16112) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) at android.view.View.performClickInternal(View.java:7769) at android.view.View.access$3800(View.java:910) at android.view.View$PerformClick.run(View.java:30218) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8751) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135) logger> exception: Unhandled biometric status: java.lang.Exception[Mode 15 encountered unexpected status 1] logger> maintenance: Biometric not available during biometric configuration

@Jignesh-dimagi
Copy link
Contributor

Futher navigateToMessageDisplay() is solely created to navigate to personalid_message_display screen and as per condition applied it will not apply navigation only when current screen is personalid_message_display in this case it will cancel navigation

@jaypanchal-13 While this feels fine for message, I think introducing these kind of checks is a bad practice for overall health of code as I am pretty sure we will need to introduce similar checks for other fragment transitions as well and not just messages. If we except this solution here we will have to accept doing these checks for other fragment transitions which will decrease our overall code health in terms of managing these.
In our experience with PersonalID launch, These errors are always a result of a something amiss in our code with respect to navigation stack management. We have gone back and forth to adding these checks and later realising that the actual solution was correcting the nav stack is some part of the code which was causing these errors.
I also see in the crashes linked, that this always seem to happen when there is no biometric hardware present on the device, have you been able to test that workflow ? Screenshot 2025-09-15 at 10 28 47 AM

@shubham1g5 Yes I verified and tested this before but was not able to reproduce it. And I tested now I am getting some crash printed in warring logs when biometric button is clicked and when there is some issue with biometric. I think this might be the case. What do you think on it?

java.lang.Exception: Mode 15 encountered unexpected status 1 at org.commcare.utils.BiometricsHelper.checkStatus(BiometricsHelper.java:181) at org.commcare.utils.BiometricsHelper.checkFingerprintStatus(BiometricsHelper.java:54) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.onFingerprintButtonClicked(PersonalIdBiometricConfigFragment.java:192) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.lambda$onCreateView$0(PersonalIdBiometricConfigFragment.java:76) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment.$r8$lambda$Rw9nj2U3dnIrBY4yRk8Cw4fw-L4(PersonalIdBiometricConfigFragment.java:0) at org.commcare.fragments.personalId.PersonalIdBiometricConfigFragment$$ExternalSyntheticLambda1.onClick(R8$$SyntheticClass:0) at android.view.View.performClick(View.java:7792) at android.widget.TextView.performClick(TextView.java:16112) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) at android.view.View.performClickInternal(View.java:7769) at android.view.View.access$3800(View.java:910) at android.view.View$PerformClick.run(View.java:30218) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8751) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135) logger> exception: Unhandled biometric status: java.lang.Exception[Mode 15 encountered unexpected status 1] logger> maintenance: Biometric not available during biometric configuration

@jaypanchal-13 Mode 15 encountered unexpected is internal error from Android OS and I guess its not related to the issue you are trying to solve. What we require here is to correct the flow which is causing this issue instead of silently handling this errors in app. This has become important now as we are going with Navigation in many other pages so we should have this learning where we are making these mistakes.

@jaypanchal-13
Copy link
Contributor Author

jaypanchal-13 commented Sep 15, 2025

@Jignesh-dimagi Mode %d encountered unexpected is a log added on deafult when no cases meet for biometric. So I don't think it might be internal error from Android OS. In my device biometric feature is available but fingerprint sensor not working and this case was not handled so I am getting such warring logs(not error or crash) and I think this might be the issue of this crash(as we are not able to reproduce it).
BIOMETRIC_ERROR_HW_UNAVAILABLE (value = 1) is missing if I adds it. The given problem which I suggested is solved. I think this might be issue for crash(not sure). But not able to reproduce it

@Jignesh-dimagi
Copy link
Contributor

@Jignesh-dimagi Mode %d encountered unexpected is a log added on deafult when no cases meet for biometric. So I don't think it might be internal error from Android OS. In my device biometric feature is available but fingerprint sensor not working and this case was not handled so I am getting such warring logs(not error or crash) and I think this might be the issue of this crash(as we are not able to reproduce it).
BIOMETRIC_ERROR_HW_UNAVAILABLE (value = 1) is missing if I adds it. The given problem which I suggested is solved. I think this might be issue for crash(not sure). But not able to reproduce it

@jaypanchal-13 Exactly when I say internal to Android OS means its related to biometric and not related to navigation. Also, I think all Biometric related error cases are handled here. I have feeling that navigation is failing due to message fragment not having proper nav implemented.

@jaypanchal-13
Copy link
Contributor Author

@Jignesh-dimagi Mode %d encountered unexpected is a log added on deafult when no cases meet for biometric. So I don't think it might be internal error from Android OS. In my device biometric feature is available but fingerprint sensor not working and this case was not handled so I am getting such warring logs(not error or crash) and I think this might be the issue of this crash(as we are not able to reproduce it).
BIOMETRIC_ERROR_HW_UNAVAILABLE (value = 1) is missing if I adds it. The given problem which I suggested is solved. I think this might be issue for crash(not sure). But not able to reproduce it

@jaypanchal-13 Exactly when I say internal to Android OS means its related to biometric and not related to navigation. Also, I think all Biometric related error cases are handled here. I have feeling that navigation is failing due to message fragment not having proper nav implemented.

@Jignesh-dimagi I think timing might be the issue for crashes which are seen related to navigation and for biometric crash it might be due to missing some edge cases or timing, one of them I mentioned above. This issue is not reproducible. What do you suggest @shubham1g5

@shubham1g5
Copy link
Contributor

@jaypanchal-13 Have you tried forcing the ConfigurationStatus.NotAvailable; from checkStatus to reproduce this issue ?

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 Have you tried forcing the ConfigurationStatus.NotAvailable; from checkStatus to reproduce this issue ?

@shubham1g5 yes tried it. Not reproducible

@shubham1g5
Copy link
Contributor

@jaypanchal-13 Happy to close this out as won't do in that case, don't think we can merge this solution and also the crash is on rare side (19 events in last 30 days)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants