Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ internal class OneSignalImp(
@Volatile
private var initState: InitState = InitState.NOT_STARTED

// Save the exception pointing to the caller that triggered init, not the async worker thread.
private var initFailureException: Exception? = null

override val sdkVersion: String = OneSignalUtils.sdkVersion

override val isInitialized: Boolean
Expand Down Expand Up @@ -279,7 +282,11 @@ internal class OneSignalImp(
val startupService = bootstrapServices()
val result = resolveAppId(appId, configModel, preferencesService)
if (result.failed) {
Logging.warn("suspendInitInternal: no appId provided or found in legacy config.")
val message = "No OneSignal appId provided or found in local storage. Please pass a valid appId to initWithContext()."
val exception = IllegalStateException(message)
// attach the real crash cause to the init failure exception that will be throw shortly after
initFailureException?.addSuppressed(exception)
Logging.warn(message)
initState = InitState.FAILED
notifyInitComplete()
return false
Expand Down Expand Up @@ -395,12 +402,12 @@ internal class OneSignalImp(

// Re-check state after waiting - init might have failed during the wait
if (initState == InitState.FAILED) {
throw IllegalStateException("Initialization failed. Cannot proceed.")
throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")
}
// initState is guaranteed to be SUCCESS here - consistent state
}
InitState.FAILED -> {
throw IllegalStateException("Initialization failed. Cannot proceed.")
throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")
}
else -> {
// SUCCESS - already initialized, no need to wait
Expand Down Expand Up @@ -506,6 +513,9 @@ internal class OneSignalImp(
): Boolean {
Logging.log(LogLevel.DEBUG, "initWithContext(context: $context, appId: $appId)")

// This ensures the stack trace points to the caller that triggered init, not the async worker thread.
initFailureException = IllegalStateException("OneSignal initWithContext failed.")

// Use IO dispatcher for initialization to prevent ANRs and optimize for I/O operations
return withContext(ioDispatcher) {
// do not do this again if already initialized or init is in progress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,6 @@ class SDKInitTests : FunSpec({

beforeAny {
Logging.logLevel = LogLevel.NONE

// Aggressive pre-test cleanup to avoid state leakage across tests
val context = getApplicationContext<Context>()
val prefs = context.getSharedPreferences("OneSignal", Context.MODE_PRIVATE)
prefs.edit()
.clear()
.commit()

val otherPrefs = context.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE)
otherPrefs.edit()
.clear()
.commit()

Thread.sleep(100)
}

afterAny {
Expand All @@ -65,19 +51,27 @@ class SDKInitTests : FunSpec({
otherPrefs.edit()
.clear()
.commit()
}

// Wait longer to ensure cleanup is complete
Thread.sleep(100)
test("accessor instances after initWithContext without appID shows the failure reason") {
// Given
val context = getApplicationContext<Context>()
val os = OneSignalImp()

// Clear any in-memory state by initializing and logging out a fresh instance
try {
val os = OneSignalImp()
os.initWithContext(context, "appId")
os.logout()
Thread.sleep(100)
} catch (ignored: Exception) {
// ignore cleanup exceptions
// When
os.initWithContext(context)

// Then
val ex = shouldThrow<IllegalStateException> {
os.user.onesignalId // Should trigger waitUntilInitInternal → throw failure message
}
ex.message shouldBe "suspendInitInternal: no appId provided or found in local storage. Please pass a valid appId to initWithContext()."

// Calling initWithContext with an appID after the failure should not throw anymore
val result = os.initWithContext(context, "appID")
waitForInitialization(os)
result shouldBe true
os.isInitialized shouldBe true
}

test("OneSignal accessors throw before calling initWithContext") {
Expand Down
Loading