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
16 changes: 16 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 @@ -61,6 +62,20 @@ android {
exclude 'androidsupportmultidexversion.txt'
}

// Exclude deprecated Java MainApplication from compilation to prevent conflicts
sourceSets {
main {
java {
exclude '**/MainApplication.java'
}
}
}

// Alternative approach: exclude from all source sets
android.sourceSets.all { sourceSet ->
sourceSet.java.exclude '**/MainApplication.java'
}

task flavorSelection() {
def tasksList = gradle.startParameter.taskRequests.toString()
if (tasksList.contains('Gms')) {
Expand All @@ -74,6 +89,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,29 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* @deprecated This Java implementation is deprecated. Use {@link MainApplicationKT} instead.
* The Kotlin version provides better async handling and modern coroutines support.
*
* NOTE: This file is excluded from compilation in build.gradle to prevent conflicts.
* It's kept for reference only and will be removed in a future version.
*/
@Deprecated(since = "5.4.0", forRemoval = true)
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,154 @@
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 (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.Dispatchers
import kotlinx.coroutines.launch

class MainApplicationKT : MultiDexApplication() {
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
CoroutineScope(Dispatchers.IO).launch {
val success = OneSignal.initWithContextSuspend(this@MainApplicationKT, appId)
Log.d(Tag.LOG_TAG, "OneSignal async init success: $success")

if (success) {
// 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()
}
Loading
Loading