-
-
Notifications
You must be signed in to change notification settings - Fork 45
Solved crash FragmentManager is already executing transactions #3339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughThe unlocking logic in ConnectUnlockFragment was moved from onCreateView to onViewCreated. onCreateView now only inflates and returns the view. A new onViewCreated override triggers PersonalIdManager.getInstance().unlockConnect with callbacks: on success, retrieveOpportunities is executed; on failure, the hosting activity is finished. No other logic changes or navigation alterations were introduced. One new public method signature was added: onViewCreated(@nonnull View, @nullable Bundle). Sequence Diagram(s)sequenceDiagram
autonumber
actor OS as Android OS
participant F as ConnectUnlockFragment
participant P as PersonalIdManager
participant A as Activity
OS->>F: onCreateView()
Note right of F: Inflate view and return\n(binding.getRoot())
OS->>F: onViewCreated(view, savedInstanceState)
F->>P: unlockConnect(callback)
alt unlock success
P-->>F: onSuccess
F->>F: retrieveOpportunities()
Note right of F: Subsequent navigation\nunchanged
else unlock failure
P-->>F: onFailure
F->>A: finish()
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Suggested reviewers
Pre-merge checks (1 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
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.
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 unit tests
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. Comment |
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java (1)
31-36: Null out view binding in onDestroyView to avoid leaks (team convention).This fragment uses view binding but doesn’t clear it. Add the standard cleanup.
class ConnectUnlockFragment extends Fragment { private FragmentConnectUnlockBinding binding; @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + }
🧹 Nitpick comments (4)
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java (4)
61-71: Post biometric launch to the next frame to fully exit FragmentManager’s transaction.onViewCreated can still run while FragmentManager is executing the add/replace transaction. Post to the view to guarantee we’re outside that critical section and guard for detachment.
Apply:
- PersonalIdManager.getInstance().unlockConnect((CommCareActivity<?>) requireActivity(), success -> { - if (success) { - retrieveOpportunities(); - } else { - requireActivity().finish(); - } - }); + view.post(() -> { + if (!isAdded()) { return; } + CommCareActivity<?> activity = (CommCareActivity<?>) requireActivity(); + PersonalIdManager.getInstance().unlockConnect(activity, success -> { + if (!isAdded()) { return; } + if (success) { + retrieveOpportunities(); + } else if (!requireActivity().isFinishing() && !requireActivity().isDestroyed()) { + requireActivity().finish(); + } + }); + });Please re-check the original crash path on a few devices/AndroidX versions after this tweak.
73-92: Guard async callbacks against detached fragment before navigating.Network callbacks can return after view destruction. Add a quick attachment check before redirection to prevent IllegalStateException.
private void retrieveOpportunities() { ConnectUserRecord user = ConnectUserDatabaseUtil.getUser(getContext()); new ConnectApiHandler<ConnectOpportunitiesResponseModel>() { @@ @Override public void onSuccess(ConnectOpportunitiesResponseModel data) { if (!data.getValidJobs().isEmpty()) { ConnectJobUtils.storeJobs(requireContext(), data.getValidJobs(), true); ConnectUserDatabaseUtil.turnOnConnectAccess(requireContext()); } - setFragmentRedirection(); + if (isAdded()) { + setFragmentRedirection(); + } } }.getConnectOpportunities(requireContext(), user); }
74-74: Prefer requireContext() here.Avoid possible null; consistent with the rest of the file.
- ConnectUserRecord user = ConnectUserDatabaseUtil.getUser(getContext()); + ConnectUserRecord user = ConnectUserDatabaseUtil.getUser(requireContext());
99-130: Navigation: remove redundant popBackStack and decouple from binding root.popUpTo(inclusive=true) already clears back stack; the extra popBackStack is unnecessary. Also prefer NavHostFragment.findNavController(this) to avoid reliance on a non-null view/binding.
- NavController navController = Navigation.findNavController(binding.getRoot()); - navController.popBackStack(); + NavController navController = NavHostFragment.findNavController(this); NavOptions options = new NavOptions.Builder() .setPopUpTo(navController.getGraph().getStartDestinationId(), true, true) .build(); navController.navigate(fragmentId, bundle, options);Add import:
import androidx.navigation.fragment.NavHostFragment;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3108
File: app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java:62-64
Timestamp: 2025-06-04T19:17:21.213Z
Learning: In ConnectUnlockFragment.java, the user prefers to let getArguments() potentially throw NullPointerException rather than adding null checks, as the arguments are required for proper navigation flow and their absence indicates a programming error that should fail fast.
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3248
File: app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java:0-0
Timestamp: 2025-07-29T14:10:55.131Z
Learning: In ConnectUnlockFragment.java, opportunityId values are expected to always contain valid integer strings. Integer.parseInt() should be allowed to throw NumberFormatException if invalid data is encountered, following the fail-fast philosophy used throughout the Connect feature for data contract violations.
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2949
File: app/src/org/commcare/fragments/connectId/ConnectIdBiometricConfigFragment.java:235-236
Timestamp: 2025-02-04T21:29:29.594Z
Learning: The empty performPasswordUnlock method in ConnectIdBiometricConfigFragment is intentionally left empty and should not be flagged in reviews.
📚 Learning: 2025-06-04T19:17:21.213Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3108
File: app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java:62-64
Timestamp: 2025-06-04T19:17:21.213Z
Learning: In ConnectUnlockFragment.java, the user prefers to let getArguments() potentially throw NullPointerException rather than adding null checks, as the arguments are required for proper navigation flow and their absence indicates a programming error that should fail fast.
Applied to files:
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java
📚 Learning: 2025-07-29T14:10:55.131Z
Learnt from: OrangeAndGreen
PR: dimagi/commcare-android#3248
File: app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java:0-0
Timestamp: 2025-07-29T14:10:55.131Z
Learning: In ConnectUnlockFragment.java, opportunityId values are expected to always contain valid integer strings. Integer.parseInt() should be allowed to throw NumberFormatException if invalid data is encountered, following the fail-fast philosophy used throughout the Connect feature for data contract violations.
Applied to files:
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java
📚 Learning: 2025-02-04T21:29:29.594Z
Learnt from: pm-dimagi
PR: dimagi/commcare-android#2949
File: app/src/org/commcare/fragments/connectId/ConnectIdBiometricConfigFragment.java:235-236
Timestamp: 2025-02-04T21:29:29.594Z
Learning: The empty performPasswordUnlock method in ConnectIdBiometricConfigFragment is intentionally left empty and should not be flagged in reviews.
Applied to files:
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java
📚 Learning: 2025-03-10T08:16:59.436Z
Learnt from: shubham1g5
PR: dimagi/commcare-android#2949
File: app/src/org/commcare/fragments/connectId/ConnectIDSecondaryPhoneNumber.java:58-59
Timestamp: 2025-03-10T08:16:59.436Z
Learning: All fragments using view binding should implement proper cleanup in onDestroyView() by setting binding to null to prevent memory leaks.
Applied to files:
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java
🧬 Code graph analysis (1)
app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java (1)
app/src/org/commcare/connect/PersonalIdManager.java (1)
PersonalIdManager(64-621)
⏰ 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/connect/ConnectUnlockFragment.java (1)
58-59: Good move: stop triggering BiometricPrompt in onCreateView.Returning the view here and deferring auth out of onCreateView directly addresses the FragmentManager “already executing transactions” crash.
|
@jaypanchal-13 Think Jignesh was able to repro this already on clicking a notification and is addressing this as part of this |
Crash Description
Ticket -> https://dimagi.atlassian.net/browse/CCCT-1620
RCA
The crash was caused because
BiometricPrompt.authenticate()adds an internal fragment to FragmentManager. We were calling it insideonCreateView(), while FragmentManager was still inflating ConnectUnlockFragment, leading to:IllegalStateException: FragmentManager is already executing transactions. the issue was timing of the callSolution
By moving the call into
onViewCreated(), we ensure the fragment’s view is fully created and FragmentManager is idle before biometric is triggered. This avoids transaction conflicts.Feature Flag
Safety Assurance
Safety story
Automated test coverage
QA Plan
Labels and Review