Repository navigation
fix: [SDK-5538] never crash on invalid init or calls before init - #2803
Draft
abdulraqeeb33 wants to merge 3 commits into
Draft
abdulraqeeb33 wants to merge 3 commits into
abdulraqeeb33 wants to merge 3 commits into
Conversation
Contributor
📊 Diff Coverage ReportDiff 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
Overall (aggregate gate)242/259 touched executable lines covered (93.4% — requires ≥ 80%) |
OneSignalImpV2 becomes the never-crash implementation (the default; the kill switch still selects OneSignalImp): - initWithContext accepts a null context or appId. An empty, null or NUL appId falls back to a validated cached appId; with none, init is skipped and logged. - Init state lives in InitGate: waiters follow retries, a cancelled suspend init ends in FAILED, a retry skips services that already bootstrapped, and consent set while init finishes is kept. - Calls before init log and return a default. Accessors hand out the deferred managers, whose queued registrations replay before services start and before SUCCESS. OneSignalImp only takes the nullable signatures; a null context still throws there. Co-authored-by: Cursor <cursoragent@cursor.com>
…ed before init A wait for init from the thread running init (for example a listener replayed during init that calls back into OneSignal) now returns not-ready instead of waiting on itself; a failed attempt finishing late never clears a retry's claim. A call on an interrupted thread no longer throws InterruptedException: only an in-progress wait enters runBlocking, and an interrupt there returns not-ready and keeps the flag. Every init attempt now ends: a LinkageError, or a block that returns without completing, marks it FAILED instead of leaving every sync accessor blocked. Each ignored call logs ERROR once per attempt (with the failure trace once) and DEBUG after that, so a polling getter cannot flood remote logging. Calls made before any attempt are summarized in one ERROR when the attempt ends, success or failure, since remote logging only starts inside init; queued registrations are not counted. Documents that permission and canRequestPermission read false before init, which members each stand-in queues, and that a null appId with nothing cached warns while an invalid one errors. Trims oversized comments and makes the suspend login tests assert the switched user instead of passing on a timeout. Co-authored-by: Cursor <cursoragent@cursor.com>
abdulraqeeb33
force-pushed
the
never-crash/4-never-crash-init
branch
from
November 2, 2026 17:07
8720828 to
8e8fc05
Compare
abdulraqeeb33
force-pushed
the
never-crash/3-deferred-managers
branch
from
November 2, 2026 17:07
dc31a57 to
def35cf
Compare
7 of 18 tasks
…ired arguments With the kill switch on, OneSignalImp again throws the NullPointerException a Java null used to hit in the non-null facade: a null context on initWithContext or initWithContextSuspend, and a null appId on initWithContext. Same type and message as before, so legacy crash grouping is unchanged. KDocs on init, login and updateUserJwt now state that the app ID, external ID and token are required, and what happens when they are missing (ignored and logged, or INVALID_ARGUMENT for the composite login), instead of describing null as a supported value. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack (base
feat/never-crash, merged tomainas one unit; split out of #2793):Description
One Line Summary
OneSignalImpV2becomes the never-crash implementation: an invalid init or a call before init logs instead of crashing the app.Details
Motivation
SDK-5538 (doc): never crash the app, log instead. Today a null appId throws a Kotlin
NullPointerException, an empty appId with nothing cached still starts the SDK, and any accessor orloginbefore init throws. Wrappers (.NET, RN, Flutter, Unity) add their own guards for this; the native SDK should own it.How to read this diff
OneSignalImpV2.ktstarts as a copy ofOneSignalImp(PR 2), so its diff here is the behavior change.OneSignalImponly takes the nullable signatures and keeps its old crash for nulls; it is what the kill switch falls back to. The base tests still run againstOneSignalImp; the new tests are the*V2Testsfiles.Scope
Init
initWithContext(context: Context?, appId: String?)andinitWithContextSuspendaccept null.ensureApplicationServiceStartedthrowing: log and returnfalse.InitGate. Waiters follow a retry that starts while they wait. A cancelledinitWithContextSuspendends in FAILED, so a later init can still succeed.LinkageError, or init code that returns without finishing, marks the attempt FAILED instead of leaving every sync call blocked.StartupService), so subscriptions are not duplicated.Calls before init (NOT_STARTED or FAILED)
login,logoutandupdateUserJwtlog and return. CompositeloginreturnsNOT_INITIALIZED.InterruptedException.During init
Unchanged
Misconfigured*Manager(missing Gradle module) still throws.Kill switch on (
OneSignalImp)Identical to
main. A Java null context (either init), or a null appId oninitWithContext, throws the sameNullPointerExceptionwith the same message the non-null facade used to, so crash grouping is unchanged.Public API
StringtoString?andContexttoContext?oninitWithContextandinitWithContextSuspend. Same JVM descriptor, binary compatible,apiCheckpasses.loginandupdateUserJwtstate that the context, app ID, external ID and token are required, and what happens when one is missing (logged and ignored, orINVALID_ARGUMENTfor the compositelogin).Testing
Unit testing
InitGateTests: readiness per state,tryStart, waiting, a waiter following a retry, the init-thread guard, a late failed attempt not clearing a retry, an interrupted thread, an attempt that returns without completing, ERROR once per attempt then DEBUG, and the summary.SDKInitV2Tests,SDKInitSuspendV2Tests: null context; empty or null appId with and without a cache; accessors and login before init; captured references forward after init and after a retry; no duplicate operations on retry; replay before SUCCESS; one error per call per attempt with the trace once; aLinkageErrorduring init ends the attempt as FAILED; consent kept; cancelled suspend init can retry; JWT listeners queued; cache fallback logs WARN.AppIdHelperV2Tests,OneSignalImpV2Tests,StartupServiceTests.SDKInitTests,SDKInitSuspendTests,OneSignalImpTestsandAppIdHelperTestspass againstOneSignalImp.OneSignalImpTestsalso checks the legacy null-argument crash keeps its old type and message.Manual testing
Not yet tested on a device.
Affected code checklist
Checklist
Overview
Testing
Final pass