From cc0fa5ebd131207b349d306836409e0737f0f1f2 Mon Sep 17 00:00:00 2001 From: sherwinski Date: Tue, 7 Jul 2026 08:58:44 -0700 Subject: [PATCH 1/3] fix: register Android click listener lazily to replay cold-start clicks --- .../Runtime/AndroidNotificationsManager.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs index 4c4808b59..fd45ce63b 100644 --- a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs @@ -47,9 +47,37 @@ public AndroidNotificationsManager(AndroidJavaClass sdkClass) } public event EventHandler ForegroundWillDisplay; - public event EventHandler Clicked; public event EventHandler PermissionChanged; + // Only set the native listener once + private bool _clickNativeListenerSet; + + private EventHandler _clicked; + + /// + /// The native click listener is registered lazily on the first subscription. The native SDK + /// queues clicks that occur before a listener is added (e.g. a cold start from a notification + /// tap) and replays them when one is registered, so deferring registration until a C# handler + /// exists ensures those events are not dropped. + /// + public event EventHandler Clicked + { + add + { + _clicked += value; + + if (!_clickNativeListenerSet) + { + _clickNativeListenerSet = true; + _notifications.Call( + "addClickListener", + new InternalNotificationClickListener(this) + ); + } + } + remove { _clicked -= value; } + } + public bool Permission { get => _notifications.Call("getPermission"); @@ -91,7 +119,6 @@ public void Initialize() "addForegroundLifecycleListener", new InternalNotificationLifecycleListener(this) ); - _notifications.Call("addClickListener", new InternalNotificationClickListener(this)); } private sealed class InternalPermissionObserver : OneSignalAwaitableAndroidJavaProxy @@ -194,7 +221,7 @@ public void onClick(AndroidJavaObject clickEvent) result ); - EventHandler handler = _parent.Clicked; + EventHandler handler = _parent._clicked; if (handler != null) { UnityMainThreadDispatch.Post(state => handler(_parent, args)); From e2db6d94cf3c4121fec54a31499038d4a7924fe3 Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 8 Jul 2026 11:24:43 -0700 Subject: [PATCH 2/3] fix: synchronize click listener registration and allow retry on native failure --- .../Runtime/AndroidNotificationsManager.cs | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs index fd45ce63b..4d523d3be 100644 --- a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs @@ -52,30 +52,44 @@ public AndroidNotificationsManager(AndroidJavaClass sdkClass) // Only set the native listener once private bool _clickNativeListenerSet; + private readonly object _clickRegistrationLock = new object(); + private EventHandler _clicked; /// /// The native click listener is registered lazily on the first subscription. The native SDK /// queues clicks that occur before a listener is added (e.g. a cold start from a notification /// tap) and replays them when one is registered, so deferring registration until a C# handler - /// exists ensures those events are not dropped. + /// exists ensures those events are not dropped. The handler must be attached before the + /// native call, since the replay fires as soon as the listener registers. The flag is only + /// set after a successful native call so a transient failure can retry on the next + /// subscription. /// public event EventHandler Clicked { add { - _clicked += value; - - if (!_clickNativeListenerSet) + lock (_clickRegistrationLock) + { + _clicked += value; + + if (!_clickNativeListenerSet) + { + _notifications.Call( + "addClickListener", + new InternalNotificationClickListener(this) + ); + _clickNativeListenerSet = true; + } + } + } + remove + { + lock (_clickRegistrationLock) { - _clickNativeListenerSet = true; - _notifications.Call( - "addClickListener", - new InternalNotificationClickListener(this) - ); + _clicked -= value; } } - remove { _clicked -= value; } } public bool Permission From f3d5d48310c16bc9550749fbf9b1139bf972fa45 Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 8 Jul 2026 12:14:52 -0700 Subject: [PATCH 3/3] fix: roll back managed handler when native click listener registration fails --- .../Runtime/AndroidNotificationsManager.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs index 4d523d3be..fe1f99ea1 100644 --- a/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidNotificationsManager.cs @@ -75,10 +75,20 @@ public event EventHandler Clicked if (!_clickNativeListenerSet) { - _notifications.Call( - "addClickListener", - new InternalNotificationClickListener(this) - ); + try + { + _notifications.Call( + "addClickListener", + new InternalNotificationClickListener(this) + ); + } + catch + { + // Roll back so a failed subscription has no effect; retrying won't + // add the same handler twice. + _clicked -= value; + throw; + } _clickNativeListenerSet = true; } }