Skip to content

Conversation

@Jignesh-dimagi
Copy link
Contributor

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

📝 Walkthrough

Walkthrough

This PR extends push notification handling by introducing conversion utilities and new constructors for working with notification payloads. Seven new constants are added to ConnectConstants for notification data fields. Helper methods convertPNRecordsToPayload and convertPNRecordToPayload are introduced to transform PushNotificationRecord objects into payload maps. PNApiSyncWorkerManager gains two new constructor overloads accepting notification payloads and records. FirebaseMessagingUtil gains a new getIntentForPNClick method that checks login status, converts PushNotificationRecord objects to payloads, and returns corresponding intents. PushNotificationViewModel initializes PNApiSyncWorkerManager after fetching notifications. PushNotificationActivity is updated to use getIntentForPNClick when handling notification clicks. The activity layout receives minor adjustments with RecyclerView padding and margin.

Sequence Diagram

sequenceDiagram
    participant User
    participant PushNotificationActivity
    participant FirebaseMessagingUtil
    participant AuthCheck as Login Check
    participant PayloadConversion as Payload Conversion
    participant IntentHandler

    User->>PushNotificationActivity: Click notification in list
    PushNotificationActivity->>FirebaseMessagingUtil: getIntentForPNClick(context, record)
    FirebaseMessagingUtil->>AuthCheck: cccCheckPassed(context)
    
    alt Login passed
        AuthCheck-->>FirebaseMessagingUtil: true
        FirebaseMessagingUtil->>PayloadConversion: convertPNRecordToPayload(record)
        PayloadConversion-->>FirebaseMessagingUtil: payload map
        FirebaseMessagingUtil->>IntentHandler: handleNotification(payload, false)
        IntentHandler-->>FirebaseMessagingUtil: intent
        FirebaseMessagingUtil-->>PushNotificationActivity: intent
        PushNotificationActivity->>User: Start activity
    else Login failed
        AuthCheck-->>FirebaseMessagingUtil: false
        FirebaseMessagingUtil-->>PushNotificationActivity: null
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes span multiple files with mixed concerns including new utility methods for payload conversion, constructor overloads, and integration points in the view model and activity. While individual changes are straightforward, the interconnected nature of the notification flow and payload transformation requires careful review of the conversion logic and the interaction between components.

Possibly related PRs

Suggested reviewers

  • OrangeAndGreen
  • shubham1g5

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title "Jignesh/feat/ccct 1718" appears to be formatted as a branch name rather than a descriptive pull request title. While it references real ticket identifiers (CCCT-1718 and CCCT-1629), it does not convey what the changes actually accomplish. A reviewer scanning the commit history would not understand from this title that the PR implements push notification click handling with API sync, adds constants and helper methods, or modifies the notification activity layout. The title is too vague and generic to serve as a meaningful summary of the changeset.
Description Check ⚠️ Warning The PR description is substantially incomplete compared to the required template. It includes only a "Product Description" section with two ticket links but provides no actual description of the user-facing changes or technical rationale. The description is missing critical sections including Technical Summary (no design decisions or rationale), Feature Flag information, Safety Assurance (no safety story, test coverage, or QA plan), and the Labels and Review checklist. This minimal contribution leaves reviewers without essential context for evaluating the changes. Complete the PR description by filling in all required sections: provide a brief product description of what users will experience, add a Technical Summary with design rationale linked to the tickets, specify if a feature flag applies, document the safety assurance approach (how confidence was built, test coverage, QA plan), and complete the Labels and Review checklist. The description should enable reviewers to understand both what changed and why without needing to examine every code detail.
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.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jignesh/feat/ccct-1718

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

🧹 Nitpick comments (4)
app/src/org/commcare/activities/PushNotificationActivity.kt (1)

70-74: Minor style: Add space after if keyword.

The logic correctly checks for null before starting the activity, which is appropriate given that getIntentForPNClick returns null when the CCC login check fails.

Apply this diff for consistent Kotlin style:

-                if(activityIntent!=null) {
+                if (activityIntent != null) {
app/src/org/commcare/utils/FirebaseMessagingUtil.java (1)

502-504: Consider breaking the ternary for readability.

The method logic is sound—it checks CCC login status, converts the notification record to a payload, and delegates to handleNotification. However, the ternary operator is quite long (exceeds typical line length limits).

Apply this diff to improve readability:

     public static Intent getIntentForPNClick(Context context, PushNotificationRecord pushNotificationRecord){
-        return cccCheckPassed(context) ? handleNotification(context,PushNotificationApiHelper.INSTANCE.convertPNRecordToPayload(pushNotificationRecord),null ,false): null;
+        if (!cccCheckPassed(context)) {
+            return null;
+        }
+        return handleNotification(context, 
+                PushNotificationApiHelper.INSTANCE.convertPNRecordToPayload(pushNotificationRecord), 
+                null, 
+                false);
     }
app/src/org/commcare/utils/PushNotificationApiHelper.kt (2)

84-92: Simplify list mapping with idiomatic Kotlin.

The logic is correct, but the mapping can be more concise.

Apply this diff:

     fun convertPNRecordsToPayload(pnsRecords:List<PushNotificationRecord>?): ArrayList<Map<String,String>>{
-        val pns = ArrayList<Map<String,String>>()
-        pnsRecords?.let {
-            it.map {pnRecord ->
-                pns.add(convertPNRecordToPayload(pnRecord))
-            }
-        }
-        return pns
+        return pnsRecords?.mapTo(ArrayList()) { convertPNRecordToPayload(it) }
+            ?: ArrayList()
     }

94-107: Consider using string interpolation and explicit null handling.

The conversion logic covers all required fields, but there are several areas for improvement:

  1. String concatenation: Use .toString() or string templates instead of "" + value
  2. Date formatting: Date.toString() produces a locale-dependent format that may not be suitable for API payloads or persistence
  3. Empty string handling: String concatenation with empty strings like ""+pnRecord.opportunityId will produce "null" string literals when the field is null

Consider this refactor:

     fun convertPNRecordToPayload(pnRecord: PushNotificationRecord): HashMap<String,String> {
         val pn = HashMap<String,String>()
         pn.put(REDIRECT_ACTION,pnRecord.action)
         pn.put(NOTIFICATION_TITLE,pnRecord.title)
         pn.put(NOTIFICATION_BODY,pnRecord.body)
-        pn.put(NOTIFICATION_ID,""+pnRecord.notificationId)
-        pn.put(NOTIFICATION_TIME_STAMP,pnRecord.createdDate.toString())
+        pn.put(NOTIFICATION_ID, pnRecord.notificationId)
+        pn.put(NOTIFICATION_TIME_STAMP, pnRecord.createdDate.time.toString()) // Use epoch millis
         pn.put(NOTIFICATION_STATUS,pnRecord.confirmationStatus)
-        pn.put(NOTIFICATION_MESSAGE_ID,""+pnRecord.connectMessageId)
-        pn.put(NOTIFICATION_CHANNEL_ID,""+pnRecord.channel)
-        pn.put(OPPORTUNITY_ID,""+pnRecord.opportunityId)
-        pn.put(PAYMENT_ID,""+pnRecord.paymentId)
+        pn.put(NOTIFICATION_MESSAGE_ID, pnRecord.connectMessageId)
+        pn.put(NOTIFICATION_CHANNEL_ID, pnRecord.channel)
+        pn.put(OPPORTUNITY_ID, pnRecord.opportunityId)
+        pn.put(PAYMENT_ID, pnRecord.paymentId)
         return pn
     }

If these string fields can actually be null, add explicit null handling:

pn.put(OPPORTUNITY_ID, pnRecord.opportunityId ?: "")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2689b7c and 71e825f.

📒 Files selected for processing (7)
  • app/res/layout/activity_push_notification.xml (1 hunks)
  • app/src/org/commcare/activities/PushNotificationActivity.kt (2 hunks)
  • app/src/org/commcare/activities/connect/viewmodel/PushNotificationViewModel.kt (2 hunks)
  • app/src/org/commcare/connect/ConnectConstants.java (1 hunks)
  • app/src/org/commcare/pn/workermanager/PNApiSyncWorkerManager.kt (3 hunks)
  • app/src/org/commcare/utils/FirebaseMessagingUtil.java (2 hunks)
  • app/src/org/commcare/utils/PushNotificationApiHelper.kt (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/org/commcare/utils/FirebaseMessagingUtil.java (3)
app/src/org/commcare/android/database/connect/models/PushNotificationRecord.kt (1)
  • PushNotificationRecord (12-117)
app/src/org/commcare/pn/workermanager/PNApiSyncWorkerManager.kt (1)
  • context (31-178)
app/src/org/commcare/utils/PushNotificationApiHelper.kt (1)
  • convertPNRecordToPayload (94-107)
app/src/org/commcare/pn/workermanager/PNApiSyncWorkerManager.kt (1)
app/src/org/commcare/utils/PushNotificationApiHelper.kt (1)
  • convertPNRecordsToPayload (84-92)
⏰ 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 (3)
app/res/layout/activity_push_notification.xml (1)

15-21: LGTM! Appropriate UI spacing adjustments.

The addition of paddingTop="?attr/actionBarSize" ensures the RecyclerView content clears the action bar, and the bottom margin provides appropriate spacing. These are sensible UI refinements.

app/src/org/commcare/pn/workermanager/PNApiSyncWorkerManager.kt (1)

60-63: LGTM! Constructor correctly delegates to conversion helper.

The new constructor properly converts PushNotificationRecord objects to the internal payload format using convertPNRecordsToPayload. The helper handles null input gracefully by returning an empty list, which will result in no work requests being created (acceptable behavior).

app/src/org/commcare/activities/connect/viewmodel/PushNotificationViewModel.kt (1)

42-46: No issues found. The implementation correctly uses Android WorkManager architecture.

The code properly follows the fire-and-forget pattern for background sync:

  1. Error handling is present: The PNApiSyncWorker handles failures with built-in retry logic (MAX_RETRIES = 3) and explicit Result states (Result.retry(), Result.failure()).

  2. Async design is intentional: Using WorkManager to enqueue background jobs is the correct Android pattern. The sync should not block the UI.

  3. UI updates are appropriate: Updating the UI immediately after enqueueing the sync (lines 47-49) is the expected behavior—the UI reflects notification state without waiting for the background sync to complete.

The return value (whether signaling work was queued) is ignored at the call site, which is acceptable in this architecture since WorkManager handles execution and failure states independently.

Comment on lines +49 to +55
public final static String NOTIFICATION_TITLE = "title";
public final static String NOTIFICATION_BODY = "body";
public final static String NOTIFICATION_ID = "notification_id";
public final static String NOTIFICATION_TIME_STAMP = "timestamp";
public final static String NOTIFICATION_STATUS = "status";
public final static String NOTIFICATION_MESSAGE_ID = "message_id";
public final static String NOTIFICATION_CHANNEL_ID = "channel";
Copy link

@coderabbitai coderabbitai bot Oct 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify no conflicts with existing NOTIFICATION_TITLE and NOTIFICATION_BODY.

The new constants are well-named and appropriately defined. However, note that NOTIFICATION_TITLE and NOTIFICATION_BODY are also defined in FCMMessageData (as seen in the imports). Ensure there are no conflicts or ambiguities when both are in scope.


🏁 Script executed:

#!/bin/bash
# Search for other definitions of NOTIFICATION_TITLE and NOTIFICATION_BODY
rg -nP --type=java --type=kt 'NOTIFICATION_TITLE\s*=' -C2

rg -nP --type=java --type=kt 'NOTIFICATION_BODY\s*=' -C2

# Check if both constants are used in the same files
rg -nP --type=java --type=kt -l 'ConnectConstants\.NOTIFICATION_(TITLE|BODY)' | xargs -I {} sh -c 'echo "File: {}"; rg -n "FCMMessageData\.NOTIFICATION_(TITLE|BODY)" {}'

Length of output: 80


I'll adjust the search strategy to work with the available tools. Let me run improved shell scripts without the unsupported Kotlin file type:


🏁 Script executed:

#!/bin/bash
# Search for NOTIFICATION_TITLE and NOTIFICATION_BODY definitions in Java files only
echo "=== Searching for NOTIFICATION_TITLE definitions ==="
rg -nP --type=java 'NOTIFICATION_TITLE\s*=' -C2

echo ""
echo "=== Searching for NOTIFICATION_BODY definitions ==="
rg -nP --type=java 'NOTIFICATION_BODY\s*=' -C2

Length of output: 2097


🏁 Script executed:

#!/bin/bash
# Look for FCMMessageData class and its constant definitions
echo "=== Searching for FCMMessageData class ==="
rg -nP --type=java 'class FCMMessageData' -C5

echo ""
echo "=== Searching for all NOTIFICATION constants in FCMMessageData ==="
rg -nP --type=java 'FCMMessageData' -C3 | head -50

Length of output: 5982


🏁 Script executed:

#!/bin/bash
# Search for any usage of ConnectConstants.NOTIFICATION_TITLE or ConnectConstants.NOTIFICATION_BODY
echo "=== Usage of ConnectConstants.NOTIFICATION_TITLE ==="
rg -n 'ConnectConstants\.NOTIFICATION_TITLE' --type=java

echo ""
echo "=== Usage of ConnectConstants.NOTIFICATION_BODY ==="
rg -n 'ConnectConstants\.NOTIFICATION_BODY' --type=java

echo ""
echo "=== Checking for FCMMessageData usage patterns ==="
rg -n 'FCMMessageData\.NOTIFICATION' --type=java

Length of output: 412


Remove duplicate constants or consolidate with FCMMessageData—identical NOTIFICATION_TITLE and NOTIFICATION_BODY already exist.

Both NOTIFICATION_TITLE and NOTIFICATION_BODY are already defined in FCMMessageData with identical values ("title" and "body"). The codebase currently imports and uses these from FCMMessageData (as seen in FirebaseMessagingUtil.java lines 60-61), not from ConnectConstants. Adding duplicates violates DRY principles and risks inconsistency. Either remove these constants from ConnectConstants or consolidate all notification constants into a single location.

🤖 Prompt for AI Agents
In app/src/org/commcare/connect/ConnectConstants.java around lines 49 to 55,
remove the duplicate NOTIFICATION_TITLE and NOTIFICATION_BODY constants (they
are already defined in FCMMessageData) or consolidate these notification keys
into a single constants class: delete or comment out NOTIFICATION_TITLE and
NOTIFICATION_BODY from ConnectConstants and update any references to import and
use FCMMessageData.* instead, leaving the remaining unique constants in
ConnectConstants intact to avoid duplication and maintain a single source of
truth.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Jignesh-dimagi given they are the same payload sent in 2 different places, should we be defining constraints only once as well ?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1, changed to ConnectConstants

shubham1g5
shubham1g5 previously approved these changes Oct 21, 2025
}

public static Intent getIntentForPNClick(Context context, PushNotificationRecord pushNotificationRecord){
return cccCheckPassed(context) ? handleNotification(context,PushNotificationApiHelper.INSTANCE.convertPNRecordToPayload(pushNotificationRecord),null ,false): null;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: rename handleNotification to getIntentForNotification to make it more clear that it's returning an intent that should be handled by caller.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

handleNotification is used by FCM for handling the notification depending upon the payload. It is also used when user clicks on PN either from tray or item of notification list. May be we can name as handleAndGetIntentForNotification ?

Copy link
Contributor

Choose a reason for hiding this comment

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

think fine to leave it as it is in that case.

@Jignesh-dimagi Jignesh-dimagi merged commit e0abf5f into master Oct 21, 2025
6 of 10 checks passed
@Jignesh-dimagi Jignesh-dimagi deleted the jignesh/feat/ccct-1718 branch October 21, 2025 11:25
This was referenced Oct 31, 2025
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.

3 participants