Skip to content

fix: improve push notification processing logic and deduplication handling#16

Open
pitzcarraldo wants to merge 3 commits intomainfrom
fix/dedupe-received
Open

fix: improve push notification processing logic and deduplication handling#16
pitzcarraldo wants to merge 3 commits intomainfrom
fix/dedupe-received

Conversation

@pitzcarraldo
Copy link
Contributor

@pitzcarraldo pitzcarraldo commented Dec 18, 2025

  • Refactored the push notification logic to improve processing efficiency.
  • Enhanced deduplication handling in push notification flow.

Summary by CodeRabbit

  • Bug Fixes

    • Resolved duplicate push-notification event tracking when the app is in the foreground, improving notification reliability.
    • Improved notification delivery/error recovery to reduce missed or repeated alerts.
  • Chores

    • Bumped version to 1.3.3 and updated changelog and README wording/formatting.

✏️ Tip: You can customize this high-level summary in your review settings.

@pitzcarraldo pitzcarraldo self-assigned this Dec 18, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Bumps version to 1.3.3 and refactors notification processing: renames ClixNotification.handleIncomingPayload → handleNotificationReceived and NotificationService.handleNotificationReceived → handlePushReceived, moves duplicate-message filtering into ClixNotification, and makes record/recoverReceivedMessageId internal.

Changes

Cohort / File(s) Change Summary
Version and Documentation Updates
CHANGELOG.md, README.md, gradle/libs.versions.toml, samples/basic-app/build.gradle.kts
Bumps version from 1.3.2 to 1.3.3; adds changelog entry for duplicate PUSH_NOTIFICATION_RECEIVED fix; applies minor README reflow/formatting; updates versions catalog and sample dependency comment.
Notification Handler Refactoring
clix/src/main/kotlin/so/clix/core/ClixNotification.kt, clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt, clix/src/main/kotlin/so/clix/services/NotificationService.kt
Renames handleIncomingPayloadhandleNotificationReceived in ClixNotification and expands it with duplicate suppression, foreground/background handling, and global error recovery; updates ClixMessagingService to call the renamed method; renames handleNotificationReceivedhandlePushReceived in NotificationService, removes in-method duplicate detection, exposes recordReceivedMessageId and recoverReceivedMessageId as internal, and adjusts notification channel/builder settings.

Possibly related PRs

Suggested reviewers

  • JeongwooYoo
  • nyanxyz

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: improved push notification processing logic and deduplication handling, which aligns with the refactoring of notification handlers and duplicate suppression mechanisms across multiple files.
✨ 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 fix/dedupe-received

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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 77 to 79
} catch (e: Exception) {
ClixLogger.error("Failed to handle notification received", e)
ClixLogger.error("Failed to handle push received", e)
}

Choose a reason for hiding this comment

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

P1 Badge Propagate push handling failures for dedup recovery

The new handlePushReceived swallows all exceptions (logging at lines 77-79), but ClixNotification.handleNotificationReceived now records the messageId before delegating (lines 231-235) and only clears it in its own catch (275-277). If showNotification or trackPushNotificationReceivedEvent throws, the exception is absorbed here so the caller never recovers the recorded messageId, leaving it marked as processed. Any retry or duplicate delivery of that messageId is then skipped as a duplicate, meaning transient notification/rendering failures now permanently drop the notification and receive event instead of retrying as before.

Useful? React with 👍 / 👎.

Copy link
Contributor

@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 (4)
clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt (2)

51-56: Consider adding error handling for consistency with onNewToken.

The onNewToken handler (lines 72-80) wraps its coroutine body in try-catch, but onMessageReceived does not. If handleNotificationReceived throws, the exception will be silently swallowed by the coroutine scope. Based on learnings, side effects should be handled explicitly within service classes.

🔎 Suggested improvement:
         Clix.coroutineScope.launch {
-            Clix.Notification.handleNotificationReceived(
-                notificationData = notificationData,
-                payload = payload,
-            )
+            try {
+                Clix.Notification.handleNotificationReceived(
+                    notificationData = notificationData,
+                    payload = payload,
+                )
+            } catch (e: Exception) {
+                ClixLogger.error("handleNotificationReceived failure:", e)
+            }
         }

31-31: Minor: Non-idiomatic use of .let.

The let block's return value is unused. Consider using an if statement for clarity.

🔎 Suggested improvement:
-        message.data.isNotEmpty().let { ClixLogger.debug("Message data payload: ${message.data}") }
+        if (message.data.isNotEmpty()) {
+            ClixLogger.debug("Message data payload: ${message.data}")
+        }
gradle/libs.versions.toml (1)

15-15: Update Kotlin version to 2.x in the version catalog.

Kotlin 2.x is stable and recommended (released May 2024). Consider upgrading from 1.9.25 to a current Kotlin 2.x version in the catalog to align with coding guidelines and enable modern compiler features. This change is optional if reserved for a future release.

clix/src/main/kotlin/so/clix/services/NotificationService.kt (1)

228-246: Consider reordering internal methods to follow coding guidelines.

The internal methods recordReceivedMessageId and recoverReceivedMessageId should appear before private methods to follow the guideline "Order declarations Public → Internal → Private".

As per coding guidelines, the declaration order should be Public → Internal → Private.

Suggested ordering

Move lines 228-246 to appear after line 140 (after the reset() method and before the first private method showNotification).

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa65fcc and 92f4cd6.

📒 Files selected for processing (7)
  • CHANGELOG.md (3 hunks)
  • README.md (10 hunks)
  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt (2 hunks)
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt (1 hunks)
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt (3 hunks)
  • gradle/libs.versions.toml (1 hunks)
  • samples/basic-app/build.gradle.kts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
clix/src/main/kotlin/so/clix/**

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Follow the specified library source structure under clix/src/main/kotlin/so/clix/(core|models|notification|services|utils)

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt
clix/src/main/kotlin/so/clix/{core,models}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Use sealed classes for finite states or results

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
clix/src/main/kotlin/so/clix/core/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/core/**/*.kt: Use functional error wrappers (sealed classes) in domain logic
Keep domain logic pure and side-effect-free

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
clix/src/main/kotlin/so/clix/{core,services}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Throw exceptions only for unrecoverable errors

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/**/*.kt: Use Kotlin Coroutines for asynchronous operations
Ensure thread safety for shared mutable state
Write small, pure, composable functions; favor immutability and explicit side effects
Use descriptive names and prefer expression-style functions where clear
Order declarations Public → Internal → Private
Avoid reflection, annotations, and metaprogramming unless necessary
Do not use service locators or singletons (unless absolutely necessary)
No reflection under any circumstances
Use DataStore Preferences for local storage

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt
{gradle/libs.versions.toml,**/*.gradle.kts}

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Use Firebase via BOM and version catalog

Files:

  • gradle/libs.versions.toml
  • samples/basic-app/build.gradle.kts
{gradle/libs.versions.toml,**/*.gradle.kts,detekt.yml}

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Use ktfmt, detekt, and jacoco via the Gradle version catalog

Files:

  • gradle/libs.versions.toml
  • samples/basic-app/build.gradle.kts
**/*.gradle.kts

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Use Kotlin 2.x as the minimum Kotlin version (configure Kotlin plugin accordingly)

Files:

  • samples/basic-app/build.gradle.kts
samples/**

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Place example applications under samples/

Files:

  • samples/basic-app/build.gradle.kts
clix/src/main/kotlin/so/clix/services/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/services/**/*.kt: Classes interacting with external services must use Service or Repository suffix
Handle side effects explicitly within repository/service classes

Files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/so/clix/{models,services}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/{models,services}/**/*.kt: Use kotlinx.serialization for JSON handling
Map between domain models and DTOs at the boundaries

Files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
🧠 Learnings (13)
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/** : Follow the specified library source structure under clix/src/main/kotlin/so/clix/(core|models|notification|services|utils)

Applied to files:

  • gradle/libs.versions.toml
  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to {clix/build.gradle.kts,clix/src/main/AndroidManifest.xml} : Set Android minSdk to API 21 or higher

Applied to files:

  • gradle/libs.versions.toml
  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to {build.gradle.kts,settings.gradle.kts,clix/build.gradle.kts,gradle.properties,gradle/wrapper/gradle-wrapper.properties} : Target Gradle 8.x with Kotlin DSL and JVM 21 toolchain

Applied to files:

  • samples/basic-app/build.gradle.kts
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/{models,services}/**/*.kt : Use kotlinx.serialization for JSON handling

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/{test,androidTest}/kotlin/**/*.kt : Use JUnit4/Kotest for unit tests and AndroidX Test/Robolectric for instrumentation

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/test/kotlin/so/clix/** : Mirror main source structure for unit tests under clix/src/test/kotlin/so/clix

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/**/*.kt : Use Kotlin Coroutines for asynchronous operations

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/utils/**/*.kt : Group utility classes/functions logically under utils (and its subpackages)

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/models/**/*.kt : Keep domain models serialization-agnostic (no framework annotations in core models)

Applied to files:

  • samples/basic-app/build.gradle.kts
  • README.md
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/models/**/*.kt : Define domain models as immutable data classes

Applied to files:

  • samples/basic-app/build.gradle.kts
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/**/*.kt : Write small, pure, composable functions; favor immutability and explicit side effects

Applied to files:

  • samples/basic-app/build.gradle.kts
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/services/**/*.kt : Classes interacting with external services must use Service or Repository suffix

Applied to files:

  • README.md
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/services/**/*.kt : Handle side effects explicitly within repository/service classes

Applied to files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
  • clix/src/main/kotlin/so/clix/notification/ClixMessagingService.kt
⏰ 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: build
🔇 Additional comments (9)
gradle/libs.versions.toml (1)

2-2: LGTM - Version bump aligned with changelog.

The version bump to 1.3.3 is consistent with the CHANGELOG.md entry and the PR's deduplication fix.

samples/basic-app/build.gradle.kts (1)

40-41: LGTM - Version reference updated in comment.

The commented dependency correctly reflects the new 1.3.3 version. Using project(":clix") for local development is appropriate, with the comment serving as documentation for the published artifact version.

CHANGELOG.md (1)

7-12: LGTM - Changelog entry accurately documents the fix.

The entry clearly describes the duplicate event tracking fix, follows the Keep a Changelog format, and aligns with the code changes in ClixNotification.kt and NotificationService.kt.

README.md (2)

23-23: LGTM - Version updated consistently.

The dependency version is correctly updated to 1.3.3, matching the version catalog and changelog.


3-5: Formatting changes look good.

The line wrapping adjustments throughout the README improve readability without changing content.

clix/src/main/kotlin/so/clix/services/NotificationService.kt (2)

60-80: LGTM! Clean separation of concerns.

The refactored logic correctly delegates deduplication to the calling layer while focusing this method on notification display and event tracking. The error handling appropriately catches and logs exceptions without rethrowing, following the guideline for service classes.


228-246: Well-designed deduplication helpers with proper thread safety.

The synchronized blocks ensure thread-safe access to shared state, and the conditional removal in recoverReceivedMessageId (line 241-243) correctly handles cases where the stored ID might have been replaced by a different message.

clix/src/main/kotlin/so/clix/core/ClixNotification.kt (2)

55-57: LGTM! Cleaner log formatting.

The single-line string template improves readability without changing functionality.


227-279: Excellent refactoring with robust error handling and deduplication.

The refactored flow correctly implements:

  • Early duplicate detection via recordReceivedMessageId (line 231)
  • Proper recovery when the foreground handler suppresses notifications (line 255)
  • Comprehensive error handling with nested try-catch blocks
  • Thread-safe handler access by capturing volatile references

The error recovery strategy is sound: message IDs are recovered only when suppressed or when unexpected exceptions occur in the handling logic, but not when handlePushReceived fails (since it catches exceptions internally). This prevents duplicate notifications from being shown on re-delivery while accepting that individual push failures won't be retried.

Copy link
Contributor

@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

♻️ Duplicate comments (1)
clix/src/main/kotlin/so/clix/services/NotificationService.kt (1)

60-80: Error handling now defers dedup recovery to caller.

The simplified handlePushReceived no longer manages deduplication internally. This is appropriate since ClixNotification.handleNotificationReceived now handles recordReceivedMessageId before calling this method and recoverReceivedMessageId in its own catch block (lines 276-279 in ClixNotification.kt).

However, if showNotification or trackPushNotificationReceivedEvent throws, the exception is caught here (line 77-79) and logged, but it won't propagate to the caller. This means ClixNotification won't know to recover the messageId for transient failures.

Consider re-throwing or returning a result to allow the caller to handle recovery:

🔎 Suggested approach
-    suspend fun handlePushReceived(
+    suspend fun handlePushReceived(
         payload: ClixPushNotificationPayload,
         notificationData: Map<String, Any?>,
         autoHandleLandingURL: Boolean = true,
     ) {
-        try {
-            if (hasNotificationPermission(context)) {
-                showNotification(payload, notificationData, autoHandleLandingURL)
-                trackPushNotificationReceivedEvent(
-                    payload.messageId,
-                    payload.userJourneyId,
-                    payload.userJourneyNodeId,
-                )
-                ClixLogger.debug("Message received, notification sent to Clix SDK")
-            } else {
-                ClixLogger.warn("Notification permission not granted, cannot show notification")
-            }
-        } catch (e: Exception) {
-            ClixLogger.error("Failed to handle push received", e)
+        if (hasNotificationPermission(context)) {
+            showNotification(payload, notificationData, autoHandleLandingURL)
+            trackPushNotificationReceivedEvent(
+                payload.messageId,
+                payload.userJourneyId,
+                payload.userJourneyNodeId,
+            )
+            ClixLogger.debug("Message received, notification sent to Clix SDK")
+        } else {
+            ClixLogger.warn("Notification permission not granted, cannot show notification")
         }
     }

This allows the caller (ClixNotification.handleNotificationReceived) to catch exceptions and properly recover the messageId.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92f4cd6 and e78c9f4.

📒 Files selected for processing (2)
  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt (1 hunks)
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt (5 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
clix/src/main/kotlin/so/clix/**

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Follow the specified library source structure under clix/src/main/kotlin/so/clix/(core|models|notification|services|utils)

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/so/clix/{core,models}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Use sealed classes for finite states or results

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
clix/src/main/kotlin/so/clix/core/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/core/**/*.kt: Use functional error wrappers (sealed classes) in domain logic
Keep domain logic pure and side-effect-free

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
clix/src/main/kotlin/so/clix/{core,services}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

Throw exceptions only for unrecoverable errors

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/**/*.kt: Use Kotlin Coroutines for asynchronous operations
Ensure thread safety for shared mutable state
Write small, pure, composable functions; favor immutability and explicit side effects
Use descriptive names and prefer expression-style functions where clear
Order declarations Public → Internal → Private
Avoid reflection, annotations, and metaprogramming unless necessary
Do not use service locators or singletons (unless absolutely necessary)
No reflection under any circumstances
Use DataStore Preferences for local storage

Files:

  • clix/src/main/kotlin/so/clix/core/ClixNotification.kt
  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/so/clix/services/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/services/**/*.kt: Classes interacting with external services must use Service or Repository suffix
Handle side effects explicitly within repository/service classes

Files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
clix/src/main/kotlin/so/clix/{models,services}/**/*.kt

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

clix/src/main/kotlin/so/clix/{models,services}/**/*.kt: Use kotlinx.serialization for JSON handling
Map between domain models and DTOs at the boundaries

Files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
🧠 Learnings (1)
📚 Learning: 2025-10-15T05:36:01.516Z
Learnt from: CR
Repo: clix-so/clix-android-sdk PR: 0
File: .cursor/rules/guidelines.mdc:0-0
Timestamp: 2025-10-15T05:36:01.516Z
Learning: Applies to clix/src/main/kotlin/so/clix/services/**/*.kt : Handle side effects explicitly within repository/service classes

Applied to files:

  • clix/src/main/kotlin/so/clix/services/NotificationService.kt
⏰ 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: build
🔇 Additional comments (6)
clix/src/main/kotlin/so/clix/services/NotificationService.kt (3)

180-180: Notification priority set to MAX.

PRIORITY_MAX is intended for time-critical notifications that require immediate user attention (e.g., incoming calls, alarms). For general push notifications, PRIORITY_HIGH is typically more appropriate to avoid aggressive notification behavior that users may find intrusive.

Is PRIORITY_MAX intentional for this SDK's use case? If the SDK handles various notification types, consider making the priority configurable or defaulting to PRIORITY_HIGH.


228-246: Visibility changes enable proper deduplication flow.

Making recordReceivedMessageId and recoverReceivedMessageId internal allows ClixNotification to manage the deduplication lifecycle correctly—recording before processing and recovering on failure.

Note that this approach only deduplicates against the single last-received messageId. If multiple notifications arrive in rapid succession before processing completes, earlier duplicates won't be caught. This is likely acceptable for typical push notification patterns but worth documenting.


257-257: Badge display enabled.

setShowBadge(true) enables app icon badges for this notification channel. This is a reasonable default for push notifications to improve user awareness of unread notifications.

clix/src/main/kotlin/so/clix/core/ClixNotification.kt (3)

228-236: Deduplication check at entry point is well-placed.

Recording the messageId at the start of processing and immediately returning for duplicates is the correct approach. This prevents duplicate processing regardless of where failures occur later in the flow.


240-259: Foreground handler suppression correctly recovers messageId.

When the foreground handler returns false to suppress the notification, calling recoverReceivedMessageId (line 256) ensures the message can be reprocessed if needed. The fallback to true on handler exception (line 250) is sensible—display the notification rather than silently drop it.


260-269: Background handler errors are appropriately isolated.

The background handler's exceptions are caught and logged without affecting the notification display flow. This matches the documented behavior: "notification always displayed" for background messages.

Comment on lines +271 to +279
Clix.notificationService.handlePushReceived(
payload = payload,
notificationData = notificationData,
autoHandleLandingURL = autoHandleLandingURL,
)
} catch (e: Exception) {
ClixLogger.error("Failed to handle notification received", e)
Clix.notificationService.recoverReceivedMessageId(payload.messageId)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Recovery block unreachable due to exception swallowing in handlePushReceived.

The try-catch at lines 276-279 intends to recover the messageId if handlePushReceived fails. However, handlePushReceived in NotificationService.kt catches all exceptions internally (lines 77-79) and never rethrows them. This means:

  1. The catch block here will never execute for handlePushReceived failures
  2. If showNotification or trackPushNotificationReceivedEvent fails, the messageId remains marked as processed
  3. Subsequent retries or duplicate deliveries of that message will be skipped as duplicates

This effectively makes transient failures permanent—the notification is never shown and the receive event is never tracked, with no retry possible.

To fix this, either:

  1. Remove the try-catch in handlePushReceived so exceptions propagate (recommended)
  2. Have handlePushReceived return a success/failure result that the caller can check

Based on learnings, side effects should be handled explicitly within service classes, but error propagation should allow callers to manage recovery.

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.

1 participant