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 @@ -23,29 +23,59 @@ interface IOneSignal {
* The user manager for accessing user-scoped
* management.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getUser() instead.",
replaceWith = ReplaceWith("getUser()"),
)
val user: IUserManager

/**
* The session manager for accessing session-scoped management.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getSession() instead.",
replaceWith = ReplaceWith("getSession()"),
)
val session: ISessionManager

/**
* The notification manager for accessing device-scoped
* notification management.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getNotifications() instead.",
replaceWith = ReplaceWith("getNotifications()"),
)
val notifications: INotificationsManager

/**
* The location manager for accessing device-scoped
* location management.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getLocation() instead.",
replaceWith = ReplaceWith("getLocation()"),
)
val location: ILocationManager

/**
* The In App Messaging manager for accessing device-scoped
* IAP management.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getInAppMessages() instead.",
replaceWith = ReplaceWith("getInAppMessages()"),
)
val inAppMessages: IInAppMessagesManager

/**
Expand All @@ -62,17 +92,38 @@ interface IOneSignal {
* should be set to `true` prior to the invocation of
* [initWithContext] to ensure compliance.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getConsentRequired() " +
"and setConsentRequired(required) instead.",
replaceWith = ReplaceWith("getConsentRequired()"),
)
var consentRequired: Boolean

/**
* Indicates whether privacy consent has been granted. This field is only relevant when
* the application has opted into data privacy protections. See [consentRequired].
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getConsentGiven() " +
"and setConsentGiven(value) instead.",
replaceWith = ReplaceWith("getConsentGiven()"),
)
var consentGiven: Boolean

/**
* Whether to disable the "GMS is missing" prompt to the user.
*/
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getDisableGMSMissingPrompt() " +
"and setDisableGMSMissingPrompt(value) instead.",
replaceWith = ReplaceWith("getDisableGMSMissingPrompt()"),
)
var disableGMSMissingPrompt: Boolean

/**
Expand All @@ -83,6 +134,12 @@ interface IOneSignal {
*
* @return true if the SDK could be successfully initialized, false otherwise.
*/
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function initWithContextSuspend(context, appId) instead.",
replaceWith = ReplaceWith("initWithContextSuspend(context, appId)"),
)
fun initWithContext(
context: Context,
appId: String,
Expand Down Expand Up @@ -116,11 +173,30 @@ interface IOneSignal {
* trust for the login operation. Required when identity verification has been enabled. See
* [Identity Verification | OneSignal](https://documentation.onesignal.com/docs/identity-verification)
*/
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function loginSuspend(externalId, jwtBearerToken) instead.",
replaceWith = ReplaceWith("loginSuspend(externalId, jwtBearerToken)"),
)
fun login(
externalId: String,
jwtBearerToken: String? = null,
)

/**
* Login to OneSignal under the user identified by the [externalId] provided, without a JWT
* bearer token. Convenience overload of [login] equivalent to calling it with a `null` token.
*
* @param externalId The external ID of the user that is to be logged in.
*/
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function loginSuspend(externalId) instead.",
replaceWith = ReplaceWith("loginSuspend(externalId)"),
)
@Suppress("DEPRECATION")
fun login(externalId: String) = login(externalId, null)

/**
Expand All @@ -129,6 +205,12 @@ interface IOneSignal {
* be retrieved, except through this device as long as the app remains installed and the app
* data is not cleared.
*/
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function logoutSuspend() instead.",
replaceWith = ReplaceWith("logoutSuspend()"),
)
fun logout()

/**
Expand All @@ -140,6 +222,12 @@ interface IOneSignal {
* @param externalId The external ID the JWT belongs to.
* @param token The new JWT bearer token issued by your backend.
*/
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function updateUserJwtSuspend(externalId, token) instead.",
replaceWith = ReplaceWith("updateUserJwtSuspend(externalId, token)"),
)
fun updateUserJwt(
externalId: String,
token: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
* called.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getUserSuspend() instead.",
replaceWith = ReplaceWith("getUserSuspend()"),
)
@Suppress("DEPRECATION")
val User: IUserManager
get() = oneSignal.user

Expand All @@ -50,6 +57,13 @@
* has been called.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getSessionSuspend() instead.",
replaceWith = ReplaceWith("getSessionSuspend()"),
)
@Suppress("DEPRECATION")
val Session: ISessionManager
get() = oneSignal.session

Expand All @@ -58,6 +72,13 @@
* only after [initWithContext] has been called.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getNotificationsSuspend() instead.",
replaceWith = ReplaceWith("getNotificationsSuspend()"),
)
@Suppress("DEPRECATION")
val Notifications: INotificationsManager
get() = oneSignal.notifications

Expand All @@ -66,6 +87,13 @@
* only after [initWithContext] has been called.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getLocationSuspend() instead.",
replaceWith = ReplaceWith("getLocationSuspend()"),
)
@Suppress("DEPRECATION")
val Location: ILocationManager
get() = oneSignal.location

Expand All @@ -74,6 +102,13 @@
* only after [initWithContext] has been called.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend function getInAppMessagesSuspend() instead.",
replaceWith = ReplaceWith("getInAppMessagesSuspend()"),
)
@Suppress("DEPRECATION")
val InAppMessages: IInAppMessagesManager
get() = oneSignal.inAppMessages

Expand All @@ -94,7 +129,15 @@
* [initWithContext] to ensure compliance.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getConsentRequiredSuspend() " +
"and setConsentRequiredSuspend(required) instead.",
replaceWith = ReplaceWith("getConsentRequiredSuspend()"),
)
@Suppress("DEPRECATION")
var consentRequired: Boolean

Check warning on line 140 in OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt

View check run for this annotation

Claude / Claude Code Review

Var properties: @Deprecated annotation applies to both accessors

The three deprecated `var` properties (`consentRequired`, `consentGiven`, `disableGMSMissingPrompt`) carry a single property-level `@Deprecated` annotation, which Kotlin applies to *both* the getter and the setter. This causes two related issues for setter callers: (1) the "may block the calling thread… cause ANRs" message is inaccurate — the setters are non-blocking direct field writes (`OneSignalImp.kt` lines 89-94, 110-122, 134-140), and (2) the `ReplaceWith` only encodes the getter form, so
Comment on lines +132 to 140

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The three deprecated var properties (consentRequired, consentGiven, disableGMSMissingPrompt) carry a single property-level @Deprecated annotation, which Kotlin applies to both the getter and the setter. This causes two related issues for setter callers: (1) the "may block the calling thread… cause ANRs" message is inaccurate — the setters are non-blocking direct field writes (OneSignalImp.kt lines 89-94, 110-122, 134-140), and (2) the ReplaceWith only encodes the getter form, so applying the IDE quick-fix to OneSignal.consentRequired = true rewrites to OneSignal.getConsentRequiredSuspend() = true, which doesn'''t compile. Fix by splitting into @get:Deprecated / @set:Deprecated with accessor-specific messages and ReplaceWith expressions (e.g. setConsentRequiredSuspend(value) for the setter); the same pattern needs to be applied across OneSignal.kt, IOneSignal.kt, and OneSignalImp.kt.

Extended reasoning...

What the bug is

For the three deprecated mutable properties — consentRequired, consentGiven, and disableGMSMissingPrompt — the @Deprecated annotation is placed at the property level. In Kotlin, a property-level annotation on a var applies the same message and ReplaceWith to both the getter and the setter. That is fine for vals and for the blocking funs in this PR, but it produces two distinct problems for these vars.

Problem 1 — ReplaceWith quick-fix produces invalid code on setter usage

Each property uses a getter-only ReplaceWith expression, e.g. in OneSignal.kt:

replaceWith = ReplaceWith("getConsentRequiredSuspend()"),

When a developer applies the IntelliJ/Android Studio quick-fix to a setter usage like:

OneSignal.consentRequired = true

the IDE mechanically substitutes the LHS using the ReplaceWith expression, producing:

OneSignal.getConsentRequiredSuspend() = true   // does not compile — assigning to a function call

This affects setter calls that already exist in the codebase, e.g. examples/.../MainApplication.kt:65 (OneSignal.consentRequired = SharedPreferenceUtil.getCachedConsentRequired(this)), OneSignalRepository.kt:222, and the OneSignalImpTests, so it is not hypothetical.

Problem 2 — message overstates harm for setter callers

The message claims:

Accessing this property may block the calling thread until the SDK is initialized and cause ANRs when called on the main thread.

But the setters in OneSignalImp.kt never block. Reviewing the implementation:

set(value) {
    _consentRequired = value
    if (isInitialized) { configModel.consentRequired = value }
}

This writes a local backing field and (when initialized) the in-memory ConfigModel directly. None of consentRequired, consentGiven, or disableGMSMissingPrompt setters invoke blockingGet, runBlocking, or waitForInit. The only non-trivial side effect, operationRepo.forceExecuteOperations() in the consentGiven setter, just calls wake() on two waiters — also non-blocking. Only the getters route through blockingGet { ... }, which is where the ANR risk actually lives.

A common, legitimate calling pattern looks like:

OneSignal.consentRequired = true
OneSignal.initWithContext(context, appId)

The write here cannot block on init (init has not run yet — the setter falls through to the _consentRequired = value no-init branch). Telling that caller they risk ANRs is misleading.

Step-by-step proof — Problem 1

  1. A developer has OneSignal.consentRequired = true somewhere in their app.
  2. After this PR ships, the compiler reports a deprecation warning on that line.
  3. The developer clicks the IDE "Replace with" intention.
  4. The IDE looks up the ReplaceWith text — "getConsentRequiredSuspend()" — and substitutes the property reference on the LHS.
  5. The result is OneSignal.getConsentRequiredSuspend() = true, which fails to compile (error: variable expected).
  6. The deprecation message text already mentions both getConsentRequiredSuspend() and setConsentRequiredSuspend(required), so a reader can manually correct it — but the quick-fix UX is broken.

How to fix

Replace the property-level annotation with accessor-targeted ones, e.g. in OneSignal.kt:

@get:Deprecated(
    message = "Reading this property may block the calling thread until the SDK is initialized " +
        "and cause ANRs when called on the main thread. Use the suspend function getConsentRequiredSuspend() instead.",
    replaceWith = ReplaceWith("getConsentRequiredSuspend()"),
)
@set:Deprecated(
    message = "Use the suspend function setConsentRequiredSuspend(required) instead.",
    replaceWith = ReplaceWith("setConsentRequiredSuspend(value)"),
)
@JvmStatic
@Suppress("DEPRECATION")
var consentRequired: Boolean
    get() = oneSignal.consentRequired
    set(value) { oneSignal.consentRequired = value }

The same shape needs to be applied in IOneSignal.kt (with the non-suspend names setConsentRequired(required)) and in OneSignalImp.kt. With accessor-targeted annotations, the IDE quick-fix on a setter usage rewrites to OneSignal.setConsentRequiredSuspend(value) (which compiles), and the messages can be honest per accessor.

Severity / impact

No runtime impact — SDK behavior is unchanged. Compilation of existing user code is unaffected (the warning level is WARNING). What is broken is the IDE-assisted migration UX, plus message accuracy. Since the readable deprecation message does name the correct setter function, a developer who reads the warning can migrate manually. Filing as a nit.

get() = oneSignal.consentRequired
set(value) {
oneSignal.consentRequired = value
Expand All @@ -105,6 +148,14 @@
* the application has opted into data privacy protections. See [requiresPrivacyConsent].
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getConsentGivenSuspend() " +
"and setConsentGivenSuspend(value) instead.",
replaceWith = ReplaceWith("getConsentGivenSuspend()"),
)
@Suppress("DEPRECATION")
var consentGiven: Boolean
get() = oneSignal.consentGiven
set(value) {
Expand All @@ -115,6 +166,14 @@
* Whether to disable the "GMS is missing" prompt to the user.
*/
@JvmStatic
@Deprecated(
message =
"Accessing this property may block the calling thread until the SDK is initialized and " +
"cause ANRs when called on the main thread. Use the suspend functions getDisableGMSMissingPromptSuspend() " +
"and setDisableGMSMissingPromptSuspend(value) instead.",
replaceWith = ReplaceWith("getDisableGMSMissingPromptSuspend()"),
)
@Suppress("DEPRECATION")
var disableGMSMissingPrompt: Boolean
get() = oneSignal.disableGMSMissingPrompt
set(value) {
Expand All @@ -128,6 +187,13 @@
* @param appId The application ID the OneSignal SDK is bound to.
*/
@JvmStatic
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function initWithContextSuspend(context, appId) instead.",
replaceWith = ReplaceWith("initWithContextSuspend(context, appId)"),
)
@Suppress("DEPRECATION")
fun initWithContext(
context: Context,
appId: String,
Expand Down Expand Up @@ -305,6 +371,13 @@
* @param externalId The external ID of the user that is to be logged in.
*/
@JvmStatic
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function loginSuspend(externalId) instead.",
replaceWith = ReplaceWith("loginSuspend(externalId)"),
)
@Suppress("DEPRECATION")
fun login(externalId: String) = oneSignal.login(externalId)

/**
Expand All @@ -329,6 +402,13 @@
* [Identity Verification | OneSignal](https://documentation.onesignal.com/docs/identity-verification)
*/
@JvmStatic
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function loginSuspend(externalId, jwtBearerToken) instead.",
replaceWith = ReplaceWith("loginSuspend(externalId, jwtBearerToken)"),
)
@Suppress("DEPRECATION")
fun login(
externalId: String,
jwtBearerToken: String? = null,
Expand All @@ -341,6 +421,13 @@
* data is not cleared.
*/
@JvmStatic
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function logoutSuspend() instead.",
replaceWith = ReplaceWith("logoutSuspend()"),
)
@Suppress("DEPRECATION")
fun logout() = oneSignal.logout()

/**
Expand All @@ -353,6 +440,13 @@
* @param token The new JWT bearer token issued by your backend.
*/
@JvmStatic
@Deprecated(
message =
"This blocking method may block the calling thread and cause ANRs when called on the " +
"main thread. Use the suspend function updateUserJwtSuspend(externalId, token) instead.",
replaceWith = ReplaceWith("updateUserJwtSuspend(externalId, token)"),
)
@Suppress("DEPRECATION")
fun updateUserJwt(
externalId: String,
token: String,
Expand Down
Loading
Loading