-
-
Notifications
You must be signed in to change notification settings - Fork 45
CCCT-1840 Resend OTP With Fallback #3409
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
CCCT-1840 Resend OTP With Fallback #3409
Conversation
Added logic to fallback to Twilio (via Personal ID) the first time the user reattempts to send the OTP. Added a new field to PersonalIdSessionData to keep track of the number of OTP reattempts.
Formatted code. Optimized imports.
Added a firebase event log for when OTPs are requested.
Optimized imports.
Added string formatting when reporting Firebase analytics event to avoid crash due to parameter being a string value of zero.
Removed unnecessary blank lines.
Corrected java string format interpolation.
📝 WalkthroughWalkthroughThis pull request adds OTP reattempt tracking and analytics reporting to the CommCare Android Personal ID verification flow. The changes include adding an Sequence DiagramsequenceDiagram
participant User
participant Fragment as PersonalIdPhoneVerificationFragment
participant SessionData as PersonalIdSessionData
participant OtpManager
participant Analytics as FirebaseAnalyticsUtil
User->>Fragment: Request OTP (initial or resend)
Fragment->>SessionData: Get current otpReattempts
alt First attempt (reattempts = 0)
Fragment->>OtpManager: Initialize with Twilio (default)
else Reattempt (reattempts > 0)
Fragment->>Fragment: setupOtpManagerForReattempt()
Fragment->>OtpManager: Reinitialize with SMS_METHOD_PERSONAL_ID
end
Fragment->>OtpManager: Request OTP
Fragment->>SessionData: Increment otpReattempts
Fragment->>Analytics: reportOtpRequested(reattemptCount)
Analytics->>Analytics: Log OTP_REQUESTED_EVENT with OTP_REATTEMPTS param
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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
🧹 Nitpick comments (1)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
580-586: Consider improving plural formatting in analytics string.The method correctly implements OTP request reporting and follows existing patterns. However, the format string
"%d reattempt(s)"always uses "reattempt(s)" regardless of count (e.g., "1 reattempt(s)").For better grammatical correctness, consider:
- String.format("%d reattempt(s)", numberOfReattempts) + numberOfReattempts == 1 ? "1 reattempt" : String.format("%d reattempts", numberOfReattempts)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt(1 hunks)app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java(10 hunks)app/src/org/commcare/google/services/analytics/CCAnalyticsEvent.java(1 hunks)app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java(1 hunks)app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java(2 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 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-06-04T19:17:21.213Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 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/google/services/analytics/FirebaseAnalyticsUtil.java
📚 Learning: 2025-04-21T18:48:08.330Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3037
File: app/src/org/commcare/connect/ConnectConstants.java:11-15
Timestamp: 2025-04-21T18:48:08.330Z
Learning: Request codes used for startActivityForResult should be unique throughout the application, even if they're used in different activities. COMMCARE_SETUP_CONNECT_LAUNCH_REQUEST_CODE and STANDARD_HOME_CONNECT_LAUNCH_REQUEST_CODE should have different values.
Applied to files:
app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java
📚 Learning: 2025-07-29T14:10:55.131Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 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/personalId/PersonalIdPhoneVerificationFragment.java
📚 Learning: 2025-01-21T17:29:58.014Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 2912
File: app/src/org/commcare/fragments/connect/ConnectPaymentSetupFragment.java:61-66
Timestamp: 2025-01-21T17:29:58.014Z
Learning: In the CommCare Android app, for non-critical convenience features like phone number auto-population, exceptions should be logged but fail silently when there's a manual fallback available. This approach prevents app crashes while maintaining the ability to debug issues through logs.
Applied to files:
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
📚 Learning: 2025-03-10T08:16:29.416Z
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 2949
File: app/src/org/commcare/fragments/connectId/ConnectIdPasswordVerificationFragment.java:173-247
Timestamp: 2025-03-10T08:16:29.416Z
Learning: In the ConnectIdPasswordVerificationFragment, password comparisons should use MessageDigest.isEqual() rather than equals() to prevent timing attacks, and empty password validation should be implemented before verification attempts.
Applied to files:
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
📚 Learning: 2025-04-21T15:02:17.492Z
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 3042
File: app/src/org/commcare/fragments/BreadcrumbBarViewModel.java:50-55
Timestamp: 2025-04-21T15:02:17.492Z
Learning: ViewModels should not store View or Activity references as this can cause memory leaks. Unlike Fragments with setRetainInstance(true), ViewModels don't have automatic view detachment mechanisms. When migrating from Fragments to ViewModels, view references should be replaced with data-only state in the ViewModel.
Applied to files:
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
📚 Learning: 2025-06-13T15:53:12.951Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/connect/network/PersonalIdApiHandler.java:51-56
Timestamp: 2025-06-13T15:53:12.951Z
Learning: For `PersonalIdApiHandler`, the team’s convention is to propagate `JSONException` as an unchecked `RuntimeException` so the app crashes, signalling a contract/implementation bug rather than attempting a graceful retry.
Applied to files:
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
📚 Learning: 2025-05-22T14:26:41.341Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3121
File: app/src/org/commcare/fragments/SelectInstallModeFragment.java:201-205
Timestamp: 2025-05-22T14:26:41.341Z
Learning: In the Connect error handling flow of CommCare Android, error messages are shown once and then automatically cleared because the underlying error record gets deleted after being displayed. This is why there's no need for explicit methods to hide these messages.
Applied to files:
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
🧬 Code graph analysis (2)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (2)
app/src/org/commcare/google/services/analytics/CCAnalyticsEvent.java (1)
CCAnalyticsEvent(7-63)app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)
CCAnalyticsParam(7-48)
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java (5)
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
FirebaseAnalyticsUtil(44-601)app/src/org/commcare/utils/OtpManager.java (1)
OtpManager(12-42)app/src/org/commcare/utils/PersonalIDAuthService.kt (2)
activity(10-44)requestOtp(16-26)app/src/org/commcare/fragments/personalId/BasePersonalIdFragment.kt (3)
handleCommonSignupFailures(10-47)handleCommonSignupFailures(12-27)navigateToMessageDisplay(40-46)app/src/org/commcare/connect/network/connectId/PersonalIdApiErrorHandler.java (1)
PersonalIdApiErrorHandler(26-70)
⏰ 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 (10)
app/src/org/commcare/google/services/analytics/CCAnalyticsEvent.java (1)
60-60: LGTM! Analytics event constant added correctly.The new
OTP_REQUESTED_EVENTconstant follows existing naming conventions and is positioned appropriately among other Personal ID-related events.app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt (2)
10-10: LGTM! Type-safety annotation added.Adding the
@DeviceAuthTypeannotation torequiredLockimproves type safety by enforcing compile-time checks for valid authentication types.
27-27: LGTM! OTP reattempt tracking field added correctly.The
otpReattemptsfield appropriately tracks user-initiated resend attempts with a sensible default value of 0. The comment clearly explains its purpose.app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java (1)
9-11: LGTM! Required imports added.These navigation-related imports are used by the existing
getNavControllerPageChangeLoggingListener()method (lines 539-552) and should have been present.app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)
43-43: LGTM! Analytics parameter constant added correctly.The
OTP_REATTEMPTSconstant follows existing naming conventions and is appropriately used in the analytics reporting flow.app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java (5)
32-32: LGTM! Required imports added.The imports for
FirebaseAnalyticsUtilandSMS_METHOD_PERSONAL_IDare correctly added to support the new OTP analytics and fallback functionality.Also applies to: 45-45
105-117: LGTM! Auto-switch logic correctly handles Firebase failures.The automatic fallback to PersonalId SMS on non-recoverable Firebase errors is well-implemented. Correctly distinguishes between automatic failover (no reattempt increment) and user-initiated resends (tracked via
otpReattempts).
183-186: LGTM! Resend flow correctly integrated with fallback logic.The resend button now properly invokes
setupOtpManagerForReattempt()before requesting a new OTP, enabling the fallback behavior described in the PR objectives.
296-301: LGTM! Analytics correctly integrated into OTP request flow.The analytics call properly reports the current reattempt count after it's been incremented by
setupOtpManagerForReattempt(), ensuring accurate tracking of user-initiated resend attempts.
367-387: LGTM! OTP fallback logic correctly implements the specified behavior.The method correctly implements the fallback strategy described in the PR objectives:
- First resend (
otpReattempts == 0): Switches to Twilio via Personal ID SMS method- Subsequent resends: Returns to default method (Firebase from session data)
The implementation works by creating new
OtpManagerinstances without persisting the SMS method change topersonalIdSessionData, allowing subsequent resends to fall back to the original method.
Ran ktlint formatting on Kotlin files.
Fixed typos in a couple comments.
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java
Outdated
Show resolved
Hide resolved
Refactored PersonalIdPhoneVerificationFragment to always use the setupOtpManager() function to instantiate OtpManager.
Added check for the OTP fallback on the user's personal ID session data before setting up the OTP manager.
Refactored the code to report every OTP attempt to Firebase analytics rather than just reattempts.
app/src/org/commcare/android/database/connect/models/PersonalIdSessionData.kt
Outdated
Show resolved
Hide resolved
app/src/org/commcare/google/services/analytics/FirebaseAnalyticsUtil.java
Outdated
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Show resolved
Hide resolved
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
Simplified arguments for setupOtpManager().
Refactored firebase event log to use a bundle with a long for reporting otp requested events.
Got rid of the otpReattempts property from PersonalIdSessionData.
Removed unnecessary check for OTP fallback since it's already being checked in another function.
shubham1g5
left a 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.
Left a suggestion but nothing blocking.
app/src/org/commcare/google/services/analytics/CCAnalyticsEvent.java
Outdated
Show resolved
Hide resolved
| if (SMS_METHOD_PERSONAL_ID.equalsIgnoreCase(personalIdSessionData.getSmsMethod())) { | ||
| return false; | ||
| } |
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.
think this can also be part of setupOtpManager to decide on allowedToFallback. The idea here is if the default method is already SMS_METHOD_PERSONAL_ID and then fall back logic is redundant as we are already using the fallback as default. Think we can now get rid of shouldAutoSwitchToPersonalIdAuth method and directly use errorType.isNonRecoverable() in it's place.
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.
I think this could work - I want to spend some more time testing this before I push changes for it
Shortened unnecessarily long constant name.
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
Outdated
Show resolved
Hide resolved
Added a check for OTP attempts when the user comes back to the OTP screen but is still using the same session data.
CCCT-1840
Technical Summary
I implemented a fallback to Twilio (via Personal ID) when the user reattempts to send the OTP code the first time. See the ticket linked above for more details. There are no user-visible changes for this ticket.
I feel like I'm still getting familiar with this area of the code, so let me know if I should approach this ticket from a different angle and I'd be happy to make that adjustment!
Safety Assurance
Safety story
I verified that my changes work by following these testing steps:
otp_requested_eventFirebase analytics event was being logged properly and appropriately for my test device's ID.QA Plan
QA should probably follow the same steps I took but skip the last step. So here is the test plan for QA: