Skip to content

Conversation

@jaypanchal-13
Copy link
Contributor

@jaypanchal-13 jaypanchal-13 commented Jun 6, 2025

Product Description

https://dimagi.atlassian.net/browse/CCCT-1317
https://dimagi.atlassian.net/browse/QA-7817
fixed crash on otp verification

Technical Summary

Feature Flag

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 Jun 6, 2025

📝 Walkthrough

Walkthrough

The changes introduce enhanced null and emptiness checks in the OTP verification flow within PersonalIdPhoneVerificationFragment, ensuring that error messages are only displayed when valid, and that the otpManager is properly initialized and cleaned up. In FirebaseAuthService, the OTP verification callback can now be cleared via a new clearCallback() method, and its field is no longer final. The OtpManager class now stores its callback, and includes a cancel() method that clears callback references in both itself and the underlying FirebaseAuthService. No changes were made to public method signatures except for the addition of new methods.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PersonalIdPhoneVerificationFragment
    participant OtpManager
    participant FirebaseAuthService

    User->>PersonalIdPhoneVerificationFragment: Request OTP
    PersonalIdPhoneVerificationFragment->>OtpManager: requestOtp()
    OtpManager->>FirebaseAuthService: requestOtp()

    User->>PersonalIdPhoneVerificationFragment: Submit OTP
    PersonalIdPhoneVerificationFragment->>OtpManager: verifyOtp()
    OtpManager->>FirebaseAuthService: verifyOtp()
    FirebaseAuthService-->>OtpManager: Callback (success/failure)
    OtpManager-->>PersonalIdPhoneVerificationFragment: Callback (success/failure)
    alt onDestroyView
        PersonalIdPhoneVerificationFragment->>OtpManager: cancel()
        OtpManager->>FirebaseAuthService: clearCallback()
        OtpManager->>OtpManager: Clear internal callback
    end
Loading

Possibly related PRs

Suggested reviewers

  • OrangeAndGreen
  • Jignesh-dimagi
  • pm-dimagi
  • shubham1g5
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 3

🧹 Nitpick comments (3)
app/src/org/commcare/utils/FirebaseAuthService.java (2)

88-90: Consider thread safety for callback clearing.

The clearCallback() method correctly sets the callback to null. However, consider potential thread safety issues since Firebase callbacks may be invoked from background threads.

Consider adding synchronization if concurrent access is possible:

 public void clearCallback() {
+    synchronized(this) {
         this.callback = null;
+    }
 }

Also, add null checks in the callback usage within the verification callbacks (lines 36, 38, 45, 52) to prevent NPEs if callbacks are cleared during execution.


72-72: Minor: Consider retaining the more descriptive error message.

The simplified error message "Request OTP first" is less descriptive than the previous "No verification ID available. Request OTP first." which provided more context about the underlying issue.

app/src/org/commcare/utils/OtpManager.java (1)

38-43: Review the instanceof check approach.

The cancel() method correctly clears callbacks, but the instanceof check could be brittle if the implementation changes.

Consider one of these alternatives for better maintainability:

Option 1: Add clearCallback to the interface

 public interface OtpAuthService {
     void requestOtp(String phoneNumber);
     void verifyOtp(String code);
+    void clearCallback();
 }

Option 2: Store FirebaseAuthService directly

-private final OtpAuthService authService;
+private final FirebaseAuthService authService;
 
 public void cancel() {
-    if (authService instanceof FirebaseAuthService) {
-        ((FirebaseAuthService) authService).clearCallback();
-    }
+    authService.clearCallback();
     this.callback = null;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0edbc5 and 6801d62.

📒 Files selected for processing (3)
  • app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java (3 hunks)
  • app/src/org/commcare/utils/FirebaseAuthService.java (3 hunks)
  • app/src/org/commcare/utils/OtpManager.java (2 hunks)
👮 Files not reviewed due to content moderation or server errors (3)
  • app/src/org/commcare/utils/FirebaseAuthService.java
  • app/src/org/commcare/utils/OtpManager.java
  • app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
🧰 Additional context used
🧠 Learnings (1)
app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java (2)
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.
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.
🔇 Additional comments (22)
app/src/org/commcare/utils/FirebaseAuthService.java (4)

20-20: LGTM: Callback field made non-final for cleanup support.

Removing the final modifier allows the callback to be cleared via the new clearCallback() method, which is essential for preventing memory leaks and unintended callback invocations during fragment cleanup.


72-72: LGTM: Simplified error message.

The error message simplification from "No verification ID available. Request OTP first." to "Request OTP first" is appropriate and more concise while maintaining clarity.


88-90: LGTM! Clean callback cleanup implementation.

The clearCallback() method properly clears the callback reference to prevent memory leaks and unintended callback invocations.


20-20: Good change to enable callback cleanup.

Removing the final modifier allows the callback to be cleared, which is essential for preventing memory leaks.

app/src/org/commcare/utils/OtpManager.java (6)

11-11: LGTM: Callback field addition for cleanup.

Adding the callback field enables proper cleanup through the new cancel() method, which is essential for preventing memory leaks.


20-20: LGTM: Callback storage in constructor.

Storing the callback reference enables proper cleanup and follows good resource management practices.


11-11: LGTM! Proper callback storage for cleanup.

Storing the callback enables proper resource cleanup in the cancel() method.


38-43: LGTM! Well-implemented cleanup pattern.

The cancel() method properly clears callback references in both the FirebaseAuthService and itself. The instanceof check is defensive and safe.


11-11: Good addition for proper cleanup.

Storing the callback reference enables proper cleanup when the manager is canceled.


38-43: Well-implemented cleanup method.

The cancel() method properly clears callback references in both the auth service and the manager itself, preventing memory leaks. The instanceof check is appropriate since authService is declared as the interface type.

app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java (12)

102-104: LGTM: Proper null and empty check for error messages.

The null and emptiness check prevents displaying invalid error messages, which could contribute to UI crashes or poor user experience.


210-213: Excellent resource cleanup implementation.

The cleanup in onDestroyView() properly cancels the otpManager and sets it to null, preventing memory leaks and callback invocations after the fragment is destroyed. This aligns with Android fragment lifecycle best practices.

This follows the established pattern mentioned in the retrieved learnings about proper cleanup in fragment lifecycle methods.


259-259: LGTM: Defensive null check added.

The null check for otpManager prevents potential crashes during OTP requests.


266-268:

Details

✅ Verification successful

Question the re-initialization approach in verifyOtp.

While the null check prevents crashes, re-initializing otpManager if it's null in verifyOtp() might mask an underlying issue rather than properly addressing it.

This pattern suggests that otpManager can become null unexpectedly during the fragment lifecycle. Consider investigating why this happens:

Also search for similar patterns in other fragments:

Consider whether this should throw an exception or navigate back to the previous screen instead of silently re-initializing.


🏁 Script executed:

#!/bin/bash
# Search for other places where otpManager might be set to null or become null
rg -A 3 -B 3 "otpManager.*=.*null|otpManager.*null" --type java

Length of output: 2563


🏁 Script executed:

#!/bin/bash
# Search for similar re-initialization patterns in other fragments
rg -A 5 -B 2 "if.*Manager.*==.*null.*{.*init.*Manager" --type java

Length of output: 214


No issue with re-initialization of otpManager

The null-check and call to initOtpManager() in verifyOtp() is intentional: otpManager is explicitly canceled and set to null in onDestroyView() to avoid leaks, so re-initializing it before use simply ensures it’s available when the view is recreated. No further changes are needed.


102-104: LGTM! Good defensive programming for error message display.

The null and empty check prevents displaying invalid error messages, improving user experience and preventing potential UI issues.


210-213: Excellent cleanup implementation following best practices.

The proper cleanup in onDestroyView() prevents memory leaks by canceling the otpManager and setting it to null. This aligns with Android fragment lifecycle best practices and the retrieved learning about proper cleanup in fragments.


259-259: LGTM! Defensive null check prevents crashes.

The null check for otpManager before requesting OTP prevents potential null pointer exceptions and ensures the manager is properly initialized.


266-268: Good defensive initialization pattern.

Reinitializing the otpManager if it's null ensures the verification flow can proceed safely even if the manager was previously cleaned up.


102-104: Good defensive programming for error message handling.

The null and empty checks prevent displaying meaningless error messages to the user, improving the user experience.


210-213: Excellent cleanup implementation following Android best practices.

The proper cleanup of otpManager in onDestroyView() prevents memory leaks and follows Android lifecycle best practices. This aligns with the retrieved learning about proper cleanup in fragment lifecycle methods.


259-259: Good null safety check.

The null check for otpManager prevents potential NPEs during OTP requests.


266-268:

Details

✅ Verification successful

Investigate the need for otpManager re-initialization.

While the null check is good defensive programming, re-initializing otpManager in verifyOtp() suggests it might be getting cleared unexpectedly. This could indicate a timing or lifecycle issue.

Consider investigating when and why otpManager becomes null between initialization and verification:


🏁 Script executed:

#!/bin/bash
# Search for other places where otpManager might be set to null or cleared
rg -A 3 -B 3 "otpManager.*=.*null|otpManager.*=.*new|otpManager\.cancel"

# Check for any threading or lifecycle issues that might cause race conditions
ast-grep --pattern 'otpManager = $_'

Length of output: 3663


No issues with otpManager re-initialization
The null check in verifyOtp() is intentional and necessary: otpManager is explicitly canceled and set to null in onDestroyView() (around line 212) to prevent leaks, so it must be re-initialized before use. No changes are required.

public void verifyOtp(String code) {
if (verificationId == null) {
callback.onFailure("No verification ID available. Request OTP first.");
callback.onFailure("Request OTP first");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check before callback invocation.

The callback could be null if clearCallback() was called before this method execution.

if (verificationId == null) {
+   if (callback != null) {
        callback.onFailure("Request OTP first");
+   }
    return;
}
🤖 Prompt for AI Agents
In app/src/org/commcare/utils/FirebaseAuthService.java at line 72, add a null
check before invoking callback.onFailure to prevent a NullPointerException if
clearCallback() was called earlier. Wrap the callback.onFailure call inside an
if statement that checks if callback is not null.


private final FirebaseAuth firebaseAuth;
private final OtpVerificationCallback callback;
private OtpVerificationCallback callback;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null safety checks for callback usage.

Changing the callback field from final to non-final enables cleanup but introduces potential null pointer exceptions. The callback is accessed in verification callbacks (lines 36, 38, 45, 52, 72) without null checks.

Consider adding null checks before callback invocations:

@Override
public void onVerificationCompleted(@NonNull PhoneAuthCredential credential) {
    firebaseAuth.signInWithCredential(credential)
            .addOnCompleteListener(task -> {
+               if (callback == null) return;
                if (task.isSuccessful()) {
                    FirebaseUser user = task.getResult().getUser();
                    callback.onSuccess(user);
                } else {
                    callback.onFailure("Verification failed");
                }
            });
}

@Override
public void onVerificationFailed(@NonNull FirebaseException e) {
+   if (callback != null) {
        callback.onFailure("Verification failed: " + e.getMessage());
+   }
}

@Override
public void onCodeSent(@NonNull String verificationId,
                       @NonNull PhoneAuthProvider.ForceResendingToken token) {
    FirebaseAuthService.this.verificationId = verificationId;
+   if (callback != null) {
        callback.onCodeSent(verificationId);
+   }
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/org/commcare/utils/FirebaseAuthService.java at line 20, the callback
field is now non-final and may be null, but it is used without null checks in
verification callbacks at lines 36, 38, 45, 52, and 72. To fix this, add null
safety checks before each invocation of the callback to ensure it is not null,
preventing potential NullPointerExceptions during runtime.

Comment on lines 88 to 90
public void clearCallback() {
this.callback = null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null checks to prevent NPEs after callback cleanup.

The clearCallback() method is good for preventing memory leaks, but the existing callback usage in the verification callbacks (lines 36, 38, 45, 52) lacks null checks. This could cause crashes if Firebase authentication callbacks are invoked after clearCallback() is called.

Add null checks before all callback invocations:

 @Override
 public void onVerificationCompleted(@NonNull PhoneAuthCredential credential) {
     firebaseAuth.signInWithCredential(credential)
             .addOnCompleteListener(task -> {
+                if (callback == null) return;
                 if (task.isSuccessful()) {
                     FirebaseUser user = task.getResult().getUser();
                     callback.onSuccess(user);
                 } else {
                     callback.onFailure("Verification failed");
                 }
             });
 }

 @Override
 public void onVerificationFailed(@NonNull FirebaseException e) {
+    if (callback == null) return;
     callback.onFailure("Verification failed: " + e.getMessage());
 }

 @Override
 public void onCodeSent(@NonNull String verificationId,
                        @NonNull PhoneAuthProvider.ForceResendingToken token) {
     FirebaseAuthService.this.verificationId = verificationId;
+    if (callback == null) return;
     callback.onCodeSent(verificationId);
 }

Also update the verifyOtp method:

 @Override
 public void verifyOtp(String code) {
     if (verificationId == null) {
+        if (callback != null) {
             callback.onFailure("Request OTP first");
+        }
         return;
     }

     PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);
     firebaseAuth.signInWithCredential(credential)
             .addOnCompleteListener(task -> {
+                if (callback == null) return;
                 if (task.isSuccessful()) {
                     FirebaseUser user = task.getResult().getUser();
                     callback.onSuccess(user);
                 } else {
                     callback.onFailure("OTP verification failed.");
                 }
             });
 }
🤖 Prompt for AI Agents
In app/src/org/commcare/utils/FirebaseAuthService.java around lines 36 to 52 and
line 88 to 90, add null checks before invoking the callback to prevent
NullPointerExceptions after clearCallback() sets the callback to null.
Specifically, before each callback invocation in the verification callbacks,
check if the callback is not null. Also, update the verifyOtp method to include
similar null checks before calling any callback methods.

logOtpVerification(false);
Toast.makeText(requireContext(), getString(R.string.connect_otp_error) + errorMessage, Toast.LENGTH_SHORT).show();
displayOtpError(errorMessage);
if (errorMessage != null && !errorMessage.isEmpty()){
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this inside displayOtpError itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay

* @param callback Callback to handle OTP verification events
*/
public OtpManager(Activity activity, OtpVerificationCallback callback) {
this.callback = callback;
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to store the callback here, it would be cleared when you call ((FirebaseAuthService) authService).clearCallback(); anyway.

public void onDestroyView() {
super.onDestroyView();
binding = null;
if (otpManager != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary check. this can never be null here as we are initing it onCreate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 Why once Otp verified is giving another callback for Otp failure? Is this not the real issue?

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 Why once Otp verified is giving another callback for Otp failure? Is this not the real issue?

@Jignesh-dimagi yes it was, before callbacks was not cleared when changing fragment

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 Ok, just a question, if callbacks are not cleared, why it is giving failure callbacks in it? I mean what is failing?

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.

We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here

That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1

CC @shubham1g5

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 Ok, just a question, if callbacks are not cleared, why it is giving failure callbacks in it? I mean what is failing?

@Jignesh-dimagi you can check in video attached with ticket attached in this PR. If user clicks verify otp more then one time so first time otp is verified and rest of the time it is failed

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.

We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here

That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1

CC @shubham1g5

@Jignesh-dimagi real reason of crash was multiple click on verify button and this changes were also needed because otherwise on navigating back otp will be fired again as callbacks were not cleared

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.
We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here
That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1
CC @shubham1g5

@Jignesh-dimagi real reason of crash was multiple click on verify button and this changes were also needed because otherwise on navigating back otp will be fired again as callbacks were not cleared

@jaypanchal-13 If its multiple click is the issue, you can disable button after one click and enable on failure/success. OnVerificationStateChangedCallbacks is getting called here in this case when app is crashing.

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.
We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here
That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1
CC @shubham1g5

@Jignesh-dimagi real reason of crash was multiple click on verify button and this changes were also needed because otherwise on navigating back otp will be fired again as callbacks were not cleared

@jaypanchal-13 If its multiple click is the issue, you can disable button after one click and enable on failure/success. OnVerificationStateChangedCallbacks is getting called here in this case when app is crashing.

@Jignesh-dimagi yes added that

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.
We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here
That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1
CC @shubham1g5

@Jignesh-dimagi real reason of crash was multiple click on verify button and this changes were also needed because otherwise on navigating back otp will be fired again as callbacks were not cleared

@jaypanchal-13 If its multiple click is the issue, you can disable button after one click and enable on failure/success. OnVerificationStateChangedCallbacks is getting called here in this case when app is crashing.

@Jignesh-dimagi yes added that

@jaypanchal-13 Great. So only disabling the Otp verify button (not allowing the multiple clicks) and not doing all these changes (in this PR) will not solve this issue?

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 I think we don't need these changes. Problem is:

  1. Here, which has definition for firebaseAuth.signInWithCredential
  2. Here again has definition for firebaseAuth.signInWithCredential

When you verify the Otp, firebaseAuth.signInWithCredential as mentioned in point#2 is giving you the success but after some time, firebaseAuth.signInWithCredential as mentioned in point#1 is giving error.
We don't want implementation mentioned in point#1. This is for Instant verification or Auto-retrieval case, you can read here
That is the reason there are 2 callbacks, one for success and one for failure. Please remove firebaseAuth.signInWithCredential mentioned in point#1
CC @shubham1g5

@Jignesh-dimagi real reason of crash was multiple click on verify button and this changes were also needed because otherwise on navigating back otp will be fired again as callbacks were not cleared

@jaypanchal-13 If its multiple click is the issue, you can disable button after one click and enable on failure/success. OnVerificationStateChangedCallbacks is getting called here in this case when app is crashing.

@Jignesh-dimagi yes added that

@jaypanchal-13 Great. So only disabling the Otp verify button (not allowing the multiple clicks) and not doing all these changes (in this PR) will not solve this issue?

@Jignesh-dimagi it will solve current crash but as I mentioned when going back again otp was fired to solve that callback must be cleared

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 Can we remove the call back clearing code as you have made changes to directly navigate to phone fragment when user presses back button from name screen? We can keep the verify button disabling code only to protect against multiple clicks?

@Jignesh-dimagi
Copy link
Contributor

Wrong verify button enabling
One more thing here to correct. Whenever there is error in sending the Otp, Verify Code is getting enabled.

@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 Can we remove the call back clearing code as you have made changes to directly navigate to phone fragment when user presses back button from name screen? We can keep the verify button disabling code only to protect against multiple clicks?

@Jignesh-dimagi I think we should keep these code as it is best practice to clear callbacks what do you suggest @shubham1g5?

@shubham1g5
Copy link
Contributor

Cleaning out callbacks sounds good to me as we should not keep them alive once the activity is destroyed. @Jignesh-dimagi do you have any particular concerns there ?

@jaypanchal-13
Copy link
Contributor Author

Wrong verify button enabling One more thing here to correct. Whenever there is error in sending the Otp, Verify Code is getting enabled.

@Jignesh-dimagi yes it can happen that user enter wrong otp then at that type user should be able to enter correct otp next time so I enabled it

@shubham1g5
Copy link
Contributor

yes it can happen that user enter wrong otp then at that type user should be able to enter correct otp next time so I enabled it

Is it not possible to check from result if user can retry or not ?

@Jignesh-dimagi
Copy link
Contributor

yes it can happen that user enter wrong otp then at that type user should be able to enter correct otp next time so I enabled it

Is it not possible to check from result if user can retry or not ?

@jaypanchal-13 Can we use some of kind of

if (e instanceof FirebaseAuthInvalidCredentialsException) {
            // Invalid request
        } else if (e instanceof FirebaseTooManyRequestsException) {
            // The SMS quota for the project has been exceeded
        } else if (e instanceof FirebaseAuthMissingActivityForRecaptchaException) {
            // reCAPTCHA verification attempted with null Activity
        }

@jaypanchal-13
Copy link
Contributor Author

yes it can happen that user enter wrong otp then at that type user should be able to enter correct otp next time so I enabled it

Is it not possible to check from result if user can retry or not ?

@jaypanchal-13 Can we use some of kind of

if (e instanceof FirebaseAuthInvalidCredentialsException) {
            // Invalid request
        } else if (e instanceof FirebaseTooManyRequestsException) {
            // The SMS quota for the project has been exceeded
        } else if (e instanceof FirebaseAuthMissingActivityForRecaptchaException) {
            // reCAPTCHA verification attempted with null Activity
        }

@jaypanchal-13 @shubham1g5 Added checks for it

Comment on lines 24 to 28
throw new IllegalArgumentException("Phone number cannot be null or empty");
throw new IllegalArgumentException(context.getString(R.string.phone_number_cannot_be_null_or_empty));
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Just call IllegalArgumentException in OtpVerificationCallback.
Please remove the context here.

Comment on lines 31 to 35
throw new IllegalArgumentException("OTP code cannot be null or empty");
throw new IllegalArgumentException(context.getString(R.string.otp_code_cannot_be_null_or_empty));
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Same as mentioned above:
Just call IllegalArgumentException in OtpVerificationCallback.
Please remove the context here.

Comment on lines 95 to 107
Exception e = task.getException();
if (e instanceof FirebaseAuthInvalidCredentialsException) {
callback.onFailure(OtpErrorType.INVALID_CREDENTIAL, null);
} else if (e instanceof FirebaseTooManyRequestsException) {
callback.onFailure(OtpErrorType.TOO_MANY_REQUESTS, null);
} else if (e instanceof FirebaseAuthMissingActivityForRecaptchaException) {
callback.onFailure(OtpErrorType.MISSING_ACTIVITY, null);
} else if (e instanceof FirebaseAuthException) {
callback.onFailure(OtpErrorType.VERIFICATION_FAILED, null);
} else {
callback.onFailure(OtpErrorType.GENERIC_ERROR,
e != null ? e.getMessage() : "OTP verification failed");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Please create separate function to handle the exception. Code for Exception type is repeated in this class.

Comment on lines 38 to 41
callback.onFailure("Verification failed");
callback.onFailure(OtpErrorType.GENERIC_ERROR, "Verification failed");
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Please get the exception type using Exception e = task.getException(); and call function to handle the error code

Comment on lines 48 to 58
if (e instanceof FirebaseAuthInvalidCredentialsException) {
callback.onFailure(OtpErrorType.INVALID_CREDENTIAL, null);
} else if (e instanceof FirebaseTooManyRequestsException) {
callback.onFailure(OtpErrorType.TOO_MANY_REQUESTS, null);
} else if (e instanceof FirebaseAuthMissingActivityForRecaptchaException) {
callback.onFailure(OtpErrorType.MISSING_ACTIVITY, null);
} else if (e instanceof FirebaseAuthException) {
callback.onFailure(OtpErrorType.VERIFICATION_FAILED, null);
} else {
callback.onFailure(OtpErrorType.GENERIC_ERROR, e.getMessage());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Please put in separate function as code is repeated

Comment on lines 220 to 221
otpManager.cancel();
otpManager = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

@jaypanchal-13 Do we need now call back cancel and OtpManager null check as back press is going to phone fragment directly?

@Jignesh-dimagi
Copy link
Contributor

@jaypanchal-13 Can we remove the call back clearing code as you have made changes to directly navigate to phone fragment when user presses back button from name screen? We can keep the verify button disabling code only to protect against multiple clicks?

@Jignesh-dimagi I think we should keep these code as it is best practice to clear callbacks what do you suggest @shubham1g5?

Yeah, there seems 2 reasons @shubham1g5 @jaypanchal-13 :

  1. Firebase should not initiated the callback unless something is triggered by user. If app is receiving callback even when not required means something is not correct in our app, this will anyway gives issues in long run. Here, app was getting crashed as there were 2 callbacks from FirebaseAuthService due to multiple clicks happening on verify button.
  2. Firebase has activity reference so whenever activity get destroyed, it will invalid all other things.

We should keep our app code clean.

@shubham1g5
Copy link
Contributor

@Jignesh-dimagi Sounds good to me if we think the callback should not get triggered anyway after activity is destroyed, we can revisit if we see any crashes due to callback getting fired after context is detached.

…p-crash-otp

# Conflicts:
#	app/res/values/strings.xml
#	app/src/org/commcare/fragments/personalId/PersonalIdPhoneVerificationFragment.java
@jaypanchal-13
Copy link
Contributor Author

@jaypanchal-13 Can we remove the call back clearing code as you have made changes to directly navigate to phone fragment when user presses back button from name screen? We can keep the verify button disabling code only to protect against multiple clicks?

@Jignesh-dimagi I think we should keep these code as it is best practice to clear callbacks what do you suggest @shubham1g5?

Yeah, there seems 2 reasons @shubham1g5 @jaypanchal-13 :

  1. Firebase should not initiated the callback unless something is triggered by user. If app is receiving callback even when not required means something is not correct in our app, this will anyway gives issues in long run. Here, app was getting crashed as there were 2 callbacks from FirebaseAuthService due to multiple clicks happening on verify button.
  2. Firebase has activity reference so whenever activity get destroyed, it will invalid all other things.

We should keep our app code clean.

@Jignesh-dimagi @shubham1g5

  1. As soon as user enters number and navigate to otp screen callbacks are initialized in order to sent otp. So entering otp and navigating to otp screen was a trigger from user. App was getting crashed when 2 time user clicks verify otp button because callbacks are active and first time otp is verified and second time otp is not verified and 2 different callbacks were called so crash happens that issue was resolved by disabling button when user clicks first time.
  2. Yes firebase has activity reference and activity is only one PersonalIdActivity and inside it multiple fragments are created. So on destroying fragment(changing fragment) will not destroy Firebase and callbacks automatically(as activity is still active). So I have added code to clear both firebase and callbacks on destroying otp fragments(In order to avoid future crash or misbehave app can result on).

If you still want me to remove this clearing part will remove it Should I remove it.

@shubham1g5 shubham1g5 merged commit 3dd5b3f into commcare_2.57 Jun 10, 2025
1 of 2 checks passed
@shubham1g5 shubham1g5 deleted the CCCT-1317-app-crash-otp branch June 10, 2025 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-integration-tests Skip android tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants