Skip to content

fix(notifications): guard against null PendingResult from goAsync() [SDK-4803]#2667

Merged
nan-li merged 1 commit into
mainfrom
ar/sdk-4803
Jun 23, 2026
Merged

fix(notifications): guard against null PendingResult from goAsync() [SDK-4803]#2667
nan-li merged 1 commit into
mainfrom
ar/sdk-4803

Conversation

@abdulraqeeb33

Copy link
Copy Markdown
Contributor

Summary

BroadcastReceiver.goAsync() returns the Kotlin platform type PendingResult!, and the platform contract permits it to return null (for example, during background/OEM dispatch such as on vivo devices running Android 14). The receiver code dereferenced the result directly via pendingResult.finish(), which could throw a NullPointerException.

This NPE was caught and logged by suspendifyWithCompletion rather than crashing the host process, but the in-flight work was silently abandoned with no retry.

Fix

  • Declared the result as a nullable type: val pendingResult: BroadcastReceiver.PendingResult? = goAsync().
  • Changed every pendingResult.finish() call to the null-safe pendingResult?.finish() across all four receivers:
    • FCMBroadcastReceiver
    • UpgradeReceiver
    • NotificationDismissReceiver
    • BootUpReceiver

No other behavior changed.

Ticket

Linear: SDK-4803

Test plan

  • :onesignal:notifications:compileReleaseKotlin passes
  • :onesignal:notifications:testReleaseUnitTest passes

Made with Cursor

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

📊 Diff Coverage Report

Diff Coverage Report (Changed Lines Only)

Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff).

Changed Files Coverage

  • BootUpReceiver.kt: 0/3 touched executable lines (0.0%) (3 touched lines in diff)
    • 3 uncovered touched lines in this file
  • FCMBroadcastReceiver.kt: 0/5 touched executable lines (0.0%) (5 touched lines in diff)
    • 5 uncovered touched lines in this file
  • NotificationDismissReceiver.kt: 0/3 touched executable lines (0.0%) (3 touched lines in diff)
    • 3 uncovered touched lines in this file
  • UpgradeReceiver.kt: 0/3 touched executable lines (0.0%) (3 touched lines in diff)
    • 3 uncovered touched lines in this file

Overall (aggregate gate)

0/14 touched executable lines covered (0.0% — requires ≥ 80%)

Per-file detail (informational; gate is aggregate above):

  • BootUpReceiver.kt: 0.0% (3 uncovered touched lines)

  • FCMBroadcastReceiver.kt: 0.0% (5 uncovered touched lines)

  • NotificationDismissReceiver.kt: 0.0% (3 uncovered touched lines)

  • UpgradeReceiver.kt: 0.0% (3 uncovered touched lines)

❌ Coverage Check Failed

Aggregate coverage on touched lines is 0.0% (minimum 80%).

📥 View workflow run

@nan-li

nan-li commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

…SDK-4803]

BroadcastReceiver.goAsync() is a Java method returning the Kotlin platform
type PendingResult!, so the compiler inserts no null check. The platform
contract allows goAsync() to return null (observed on background/OEM dispatch,
e.g. vivo on Android 14), causing a NullPointerException when pendingResult.finish()
is called.

Declare pendingResult as an explicit nullable type and guard every finish()
dereference with a safe call across all four receivers that share this pattern.
@nan-li

nan-li commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

rebased on main

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.

2 participants