Skip to content
Merged
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
14 changes: 14 additions & 0 deletions Examples/OneSignalDemo/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}

android {
Expand Down Expand Up @@ -48,6 +49,18 @@ android {
// signingConfig null
// productFlavors.huawei.signingConfig signingConfigs.huawei
debuggable true
// Note: profileable is automatically enabled when debuggable=true
// Enable method tracing for detailed performance analysis
testCoverageEnabled false
}
// Profileable release build for performance testing
profileable {
initWith release
debuggable false
profileable true
minifyEnabled false
signingConfig signingConfigs.debug
matchingFallbacks = ['release']
}
}

Expand All @@ -74,6 +87,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
Expand Down
2 changes: 1 addition & 1 deletion Examples/OneSignalDemo/app/src/huawei/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package="com.onesignal.sdktest">

<application
android:name=".application.MainApplication">
android:name=".application.MainApplicationKT">

<service
android:name="com.onesignal.sdktest.notification.HmsMessageServiceAppLevel"
Expand Down
2 changes: 1 addition & 1 deletion Examples/OneSignalDemo/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
android:name=".application.MainApplication"
android:name=".application.MainApplicationKT"
android:allowBackup="true"
android:icon="@mipmap/ic_onesignal_launcher"
android:label="@string/app_name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,26 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* This Java implementation is not used any more. Use {@link MainApplicationKT} instead.
* The Kotlin version provides better async handling and modern coroutines support.
*
*/
public class MainApplication extends MultiDexApplication {
private static final int SLEEP_TIME_TO_MIMIC_ASYNC_OPERATION = 2000;

public MainApplication() {
// run strict mode to surface any potential issues easier
StrictMode.enableDefaults();
Log.w(Tag.LOG_TAG, "MainApplication (Java) is deprecated. Please use MainApplicationKT (Kotlin) instead.");
}

@SuppressLint("NewApi")
@Override
public void onCreate() {
super.onCreate();
Log.w(Tag.LOG_TAG, "DEPRECATED: Using MainApplication (Java). Please migrate to MainApplicationKT (Kotlin) for better async support.");

OneSignal.getDebug().setLogLevel(LogLevel.DEBUG);

// OneSignal Initialization
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.onesignal.sdktest.application

/**
* Modern Kotlin implementation of MainApplication.
*
* This replaces the deprecated MainApplication.java with:
* - Better async handling using Kotlin Coroutines
* - Modern OneSignal API usage
* - Cleaner code structure
* - Proper ANR prevention
*
* @see MainApplication.java (deprecated Java version)
*/
import android.annotation.SuppressLint
import android.os.StrictMode
import android.util.Log
import androidx.annotation.NonNull
import androidx.multidex.MultiDexApplication
import com.onesignal.OneSignal
import com.onesignal.debug.LogLevel
import com.onesignal.inAppMessages.IInAppMessageClickEvent
import com.onesignal.inAppMessages.IInAppMessageClickListener
import com.onesignal.inAppMessages.IInAppMessageDidDismissEvent
import com.onesignal.inAppMessages.IInAppMessageDidDisplayEvent
import com.onesignal.inAppMessages.IInAppMessageLifecycleListener
import com.onesignal.inAppMessages.IInAppMessageWillDismissEvent
import com.onesignal.inAppMessages.IInAppMessageWillDisplayEvent
import com.onesignal.notifications.IDisplayableNotification
import com.onesignal.notifications.INotificationClickEvent
import com.onesignal.notifications.INotificationClickListener
import com.onesignal.notifications.INotificationLifecycleListener
import com.onesignal.notifications.INotificationWillDisplayEvent
import com.onesignal.sdktest.R
import com.onesignal.sdktest.constant.Tag
import com.onesignal.sdktest.constant.Text
import com.onesignal.sdktest.notification.OneSignalNotificationSender
import com.onesignal.sdktest.util.SharedPreferenceUtil
import com.onesignal.user.state.IUserStateObserver
import com.onesignal.user.state.UserChangedState
import com.onesignal.user.state.UserState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

class MainApplicationKT : MultiDexApplication() {

private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

init {
// run strict mode to surface any potential issues easier
StrictMode.enableDefaults()
}

@SuppressLint("NewApi")
override fun onCreate() {
super.onCreate()
OneSignal.Debug.logLevel = LogLevel.DEBUG

// OneSignal Initialization
var appId = SharedPreferenceUtil.getOneSignalAppId(this)
// If cached app id is null use the default, otherwise use cached.
if (appId == null) {
appId = getString(R.string.onesignal_app_id)
SharedPreferenceUtil.cacheOneSignalAppId(this, appId)
}

OneSignalNotificationSender.setAppId(appId)

// Initialize OneSignal asynchronously on background thread to avoid ANR
applicationScope.launch {
OneSignal.initWithContextSuspend(this@MainApplicationKT, appId)
Log.d(Tag.LOG_TAG, "OneSignal async init completed")

// Set up all OneSignal listeners after successful async initialization
setupOneSignalListeners()

// Request permission - this will internally switch to Main thread for UI operations
OneSignal.Notifications.requestPermission(true)

Log.d(Tag.LOG_TAG, Text.ONESIGNAL_SDK_INIT)
}
}

private fun setupOneSignalListeners() {
OneSignal.InAppMessages.addLifecycleListener(object : IInAppMessageLifecycleListener {
override fun onWillDisplay(@NonNull event: IInAppMessageWillDisplayEvent) {
Log.v(Tag.LOG_TAG, "onWillDisplayInAppMessage")
}

override fun onDidDisplay(@NonNull event: IInAppMessageDidDisplayEvent) {
Log.v(Tag.LOG_TAG, "onDidDisplayInAppMessage")
}

override fun onWillDismiss(@NonNull event: IInAppMessageWillDismissEvent) {
Log.v(Tag.LOG_TAG, "onWillDismissInAppMessage")
}

override fun onDidDismiss(@NonNull event: IInAppMessageDidDismissEvent) {
Log.v(Tag.LOG_TAG, "onDidDismissInAppMessage")
}
})

OneSignal.InAppMessages.addClickListener(object : IInAppMessageClickListener {
override fun onClick(event: IInAppMessageClickEvent) {
Log.v(Tag.LOG_TAG, "INotificationClickListener.inAppMessageClicked")
}
})

OneSignal.Notifications.addClickListener(object : INotificationClickListener {
override fun onClick(event: INotificationClickEvent) {
Log.v(Tag.LOG_TAG, "INotificationClickListener.onClick fired" +
" with event: " + event)
}
})

OneSignal.Notifications.addForegroundLifecycleListener(object : INotificationLifecycleListener {
override fun onWillDisplay(@NonNull event: INotificationWillDisplayEvent) {
Log.v(Tag.LOG_TAG, "INotificationLifecycleListener.onWillDisplay fired" +
" with event: " + event)

val notification: IDisplayableNotification = event.notification

//Prevent OneSignal from displaying the notification immediately on return. Spin
//up a new thread to mimic some asynchronous behavior, when the async behavior (which
//takes 2 seconds) completes, then the notification can be displayed.
event.preventDefault()
val r = Runnable {
try {
Thread.sleep(SLEEP_TIME_TO_MIMIC_ASYNC_OPERATION.toLong())
} catch (ignored: InterruptedException) {
}

notification.display()
}

val t = Thread(r)
t.start()
}
})

OneSignal.User.addObserver(object : IUserStateObserver {
override fun onUserStateChange(@NonNull state: UserChangedState) {
val currentUserState: UserState = state.current
Log.v(Tag.LOG_TAG, "onUserStateChange fired " + currentUserState.toJSONObject())
}
})

OneSignal.InAppMessages.paused = true
OneSignal.Location.isShared = false
}

companion object {
private const val SLEEP_TIME_TO_MIMIC_ASYNC_OPERATION = 2000
}
}
1 change: 1 addition & 0 deletions Examples/OneSignalDemo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:8.8.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.huawei.agconnect:agcp:1.9.1.304'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,100 @@ interface IOneSignal {
* data is not cleared.
*/
fun logout()

// Suspend versions of property accessors and methods to avoid blocking threads

/**
* Initialize the OneSignal SDK, suspend until initialization is completed
*
* @param context The Android context the SDK should use.
* @param appId The application ID the OneSignal SDK is bound to.
*
* @return true if the SDK could be successfully initialized, false otherwise.
*/
suspend fun initWithContextSuspend(
context: Context,
appId: String? = null,
): Boolean

/**
* Get the session manager without blocking the calling thread.
* Suspends until the SDK is initialized.
*/
suspend fun getSession(): ISessionManager

/**
* Get the notifications manager without blocking the calling thread.
* Suspends until the SDK is initialized.
*/
suspend fun getNotifications(): INotificationsManager

/**
* Get the location manager without blocking the calling thread.
* Suspends until the SDK is initialized.
*/
suspend fun getLocation(): ILocationManager

/**
* Get the in-app messages manager without blocking the calling thread.
* Suspends until the SDK is initialized.
*/
suspend fun getInAppMessages(): IInAppMessagesManager

/**
* Get the user manager without blocking the calling thread.
* Suspends until the SDK is initialized.
*/
suspend fun getUser(): IUserManager

// Suspend versions of configuration properties for thread-safe access

/**
* Get the consent required flag in a thread-safe manner.
*/
suspend fun getConsentRequired(): Boolean

/**
* Set the consent required flag in a thread-safe manner.
*/
suspend fun setConsentRequired(required: Boolean)

/**
* Get the consent given flag in a thread-safe manner.
*/
suspend fun getConsentGiven(): Boolean

/**
* Set the consent given flag in a thread-safe manner.
*/
suspend fun setConsentGiven(value: Boolean)

/**
* Get the disable GMS missing prompt flag in a thread-safe manner.
*/
suspend fun getDisableGMSMissingPrompt(): Boolean

/**
* Set the disable GMS missing prompt flag in a thread-safe manner.
*/
suspend fun setDisableGMSMissingPrompt(value: Boolean)

/**
* Login a user with external ID and optional JWT token (suspend version).
* Handles initialization automatically.
*
* @param externalId The external ID of the user that is to be logged in.
* @param jwtBearerToken The optional JWT bearer token generated by your backend to establish
* trust for the login operation. Required when identity verification has been enabled.
* See [Identity Verification | OneSignal](https://documentation.onesignal.com/docs/identity-verification)
*/
suspend fun loginSuspend(
externalId: String,
jwtBearerToken: String? = null,
)

/**
* Logout the current user (suspend version).
*/
suspend fun logoutSuspend()
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ object OneSignal {
oneSignal.initWithContext(context, appId)
}

/**
* Initialize the OneSignal SDK asynchronously. This should be called during startup of the application.
* This method provides a suspended version that returns a boolean indicating success.
* Uses Dispatchers.IO internally to prevent ANRs and optimize for I/O operations.
*
* @param context Application context is recommended for SDK operations
* @param appId The application ID the OneSignal SDK is bound to.
* @return Boolean indicating if initialization was successful.
*/
@JvmStatic
suspend fun initWithContextSuspend(
context: Context,
appId: String? = null,
): Boolean {
return oneSignal.initWithContextSuspend(context, appId)
}

/**
* Login to OneSignal under the user identified by the [externalId] provided. The act of
* logging a user into the OneSignal SDK will switch the [User] context to that specific user.
Expand Down Expand Up @@ -208,6 +225,27 @@ object OneSignal {
return oneSignal.initWithContext(context)
}

/**
* Login a user with external ID and optional JWT token (suspend version).
*
* @param externalId External user ID for login
* @param jwtBearerToken Optional JWT token for authentication
*/
@JvmStatic
suspend fun loginSuspend(
externalId: String,
jwtBearerToken: String? = null,
) {
oneSignal.login(externalId, jwtBearerToken)
}

/**
* Logout the current user (suspend version).
*/
suspend fun logoutSuspend() {
oneSignal.logout()
}

/**
* Used to retrieve services from the SDK when constructor dependency injection is not an
* option.
Expand Down
Loading
Loading