From b17b2641503c80f204af4235bb0ee4a19fc7d23e Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 16 Jul 2026 14:40:28 -0700 Subject: [PATCH 1/3] feat(ios): add manual notification integration API --- .../ApiDefinitions.cs | 33 ++++ .../OneSignalSDK.DotNet.iOS.Binding.csproj | 1 + .../NotificationsManualIntegration.cs | 141 ++++++++++++++++++ examples/plugin-local-notif/MauiProgram.cs | 43 +++++- .../Platforms/iOS/AppDelegate.cs | 24 +++ .../Platforms/iOS/Info.plist | 2 + .../iOS/OneSignalNotificationDelegate.cs | 79 ++++++++++ examples/plugin-local-notif/README.md | 81 +++++----- examples/plugin-local-notif/run-ios.sh | 1 + 9 files changed, 356 insertions(+), 49 deletions(-) create mode 100644 OneSignalSDK.DotNet.iOS/NotificationsManualIntegration.cs create mode 100644 examples/plugin-local-notif/Platforms/iOS/OneSignalNotificationDelegate.cs diff --git a/OneSignalSDK.DotNet.iOS.Binding/ApiDefinitions.cs b/OneSignalSDK.DotNet.iOS.Binding/ApiDefinitions.cs index 8e7da62e..91f3d8e4 100644 --- a/OneSignalSDK.DotNet.iOS.Binding/ApiDefinitions.cs +++ b/OneSignalSDK.DotNet.iOS.Binding/ApiDefinitions.cs @@ -8,6 +8,15 @@ namespace Com.OneSignal.iOS { + [Internal] + [BaseType(typeof(NSObject))] + interface OneSignalCoreHelper + { + [Static] + [Export("isOneSignalPayload:")] + bool IsOneSignalPayload(NSDictionary payload); + } + // @interface OSNotification : NSObject [BaseType(typeof(NSObject))] interface OSNotification @@ -344,6 +353,30 @@ [NullAllowed] OSNotificationLifecycleListener listener //[Static, Abstract] [Export("clearAll")] void ClearAll(); + + [Export("didRegisterForRemoteNotificationsWithDeviceToken:")] + void DidRegisterForRemoteNotifications(NSData deviceToken); + + [Export("didFailToRegisterForRemoteNotificationsWithError:")] + void DidFailToRegisterForRemoteNotifications(NSError error); + + [Export("didReceiveRemoteNotification:completionHandler:")] + void DidReceiveRemoteNotification( + NSDictionary userInfo, + Action completionHandler + ); + + [Export("willPresentNotificationWithPayload:completion:")] + void WillPresentNotification( + NSDictionary payload, + OSNotificationDisplayResponse completion + ); + + [Export("didReceiveNotificationResponse:")] + void DidReceiveNotificationResponse(UNNotificationResponse response); + + [Export("setBadgeCount:")] + void SetBadgeCount(nint badgeCount); } // @interface OSPushSubscriptionChangedState : NSObject diff --git a/OneSignalSDK.DotNet.iOS.Binding/OneSignalSDK.DotNet.iOS.Binding.csproj b/OneSignalSDK.DotNet.iOS.Binding/OneSignalSDK.DotNet.iOS.Binding.csproj index b0a34785..8deae29f 100644 --- a/OneSignalSDK.DotNet.iOS.Binding/OneSignalSDK.DotNet.iOS.Binding.csproj +++ b/OneSignalSDK.DotNet.iOS.Binding/OneSignalSDK.DotNet.iOS.Binding.csproj @@ -37,6 +37,7 @@ + diff --git a/OneSignalSDK.DotNet.iOS/NotificationsManualIntegration.cs b/OneSignalSDK.DotNet.iOS/NotificationsManualIntegration.cs new file mode 100644 index 00000000..2f9932c5 --- /dev/null +++ b/OneSignalSDK.DotNet.iOS/NotificationsManualIntegration.cs @@ -0,0 +1,141 @@ +using Foundation; +using UIKit; +using UserNotifications; +using OneSignalNative = Com.OneSignal.iOS.OneSignal; + +namespace OneSignalSDK.DotNet.iOS; + +/// +/// Supported iOS notification callbacks for apps that disable OneSignal method swizzling. +/// +public static class NotificationsManualIntegration +{ + private const string DisableSwizzlingKey = "OneSignal_disable_swizzling"; + + /// + /// Returns whether manual notification forwarding is enabled in the app's Info.plist. + /// + public static bool IsEnabled => + (NSBundle.MainBundle.ObjectForInfoDictionary(DisableSwizzlingKey) as NSNumber)?.BoolValue + ?? false; + + /// + /// Returns whether a notification payload belongs to OneSignal. + /// + public static bool IsOneSignalNotification(NSDictionary payload) + { + ArgumentNullException.ThrowIfNull(payload); + return Com.OneSignal.iOS.OneSignalCoreHelper.IsOneSignalPayload(payload); + } + + /// + /// Forwards successful APNs registration to OneSignal. + /// + public static void DidRegisterForRemoteNotifications(NSData deviceToken) + { + EnsureEnabled(); + ArgumentNullException.ThrowIfNull(deviceToken); + OneSignalNative.Notifications.DidRegisterForRemoteNotifications(deviceToken); + } + + /// + /// Forwards failed APNs registration to OneSignal. + /// + public static void DidFailToRegisterForRemoteNotifications(NSError error) + { + EnsureEnabled(); + ArgumentNullException.ThrowIfNull(error); + OneSignalNative.Notifications.DidFailToRegisterForRemoteNotifications(error); + } + + /// + /// Forwards a background remote notification to OneSignal. OneSignal owns the completion handler. + /// + public static void DidReceiveRemoteNotification( + NSDictionary userInfo, + Action completionHandler + ) + { + EnsureEnabled(); + ArgumentNullException.ThrowIfNull(userInfo); + ArgumentNullException.ThrowIfNull(completionHandler); + OneSignalNative.Notifications.DidReceiveRemoteNotification(userInfo, completionHandler); + } + + /// + /// Handles a OneSignal foreground notification and completes Apple's callback exactly once. + /// + /// when the notification belonged to OneSignal. + public static bool TryHandleWillPresentNotification( + UNNotification notification, + UNNotificationPresentationOptions presentationOptions, + Action completionHandler + ) + { + EnsureEnabled(); + ArgumentNullException.ThrowIfNull(notification); + ArgumentNullException.ThrowIfNull(completionHandler); + + var payload = notification.Request.Content.UserInfo; + if (!IsOneSignalNotification(payload)) + return false; + + OneSignalNative.Notifications.WillPresentNotification( + payload, + displayableNotification => + completionHandler( + displayableNotification == null + ? (UNNotificationPresentationOptions)0 + : presentationOptions + ) + ); + return true; + } + + /// + /// Handles a OneSignal notification response and completes Apple's callback exactly once. + /// + /// when the notification belonged to OneSignal. + public static bool TryHandleNotificationResponse( + UNNotificationResponse response, + Action completionHandler + ) + { + EnsureEnabled(); + ArgumentNullException.ThrowIfNull(response); + ArgumentNullException.ThrowIfNull(completionHandler); + + if (!IsOneSignalNotification(response.Notification.Request.Content.UserInfo)) + return false; + + try + { + OneSignalNative.Notifications.DidReceiveNotificationResponse(response); + } + finally + { + completionHandler(); + } + + return true; + } + + /// + /// Sets the app icon badge count through OneSignal. + /// + public static void SetBadgeCount(nint badgeCount) + { + EnsureEnabled(); + OneSignalNative.Notifications.SetBadgeCount(badgeCount); + } + + private static void EnsureEnabled() + { + if (!IsEnabled) + { + throw new InvalidOperationException( + $"Set {DisableSwizzlingKey} to true in Info.plist before using manual integration." + ); + } + } +} diff --git a/examples/plugin-local-notif/MauiProgram.cs b/examples/plugin-local-notif/MauiProgram.cs index 16002d69..7816a4bf 100644 --- a/examples/plugin-local-notif/MauiProgram.cs +++ b/examples/plugin-local-notif/MauiProgram.cs @@ -1,6 +1,9 @@ using OneSignalSDK.DotNet; using Plugin.LocalNotification; using OsLogLevel = OneSignalSDK.DotNet.Core.Debug.LogLevel; +#if IOS +using Microsoft.Maui.LifecycleEvents; +#endif namespace PluginLocalNotifDemo; @@ -10,12 +13,6 @@ public static class MauiProgram public static MauiApp CreateMauiApp() { - var builder = MauiApp.CreateBuilder(); - - builder.UseMauiApp().UseLocalNotification(); - - var app = builder.Build(); - DotEnv.Load(); var envAppId = DotEnv.Get("ONESIGNAL_APP_ID"); @@ -24,14 +21,44 @@ public static MauiApp CreateMauiApp() ? DefaultAppId : envAppId.Trim(); - OneSignal.Debug.LogLevel = OsLogLevel.VERBOSE; - OneSignal.Initialize(appId); + var builder = MauiApp.CreateBuilder(); + + builder.UseMauiApp().UseLocalNotification(); + +#if IOS + builder.ConfigureLifecycleEvents(events => + { + events.AddiOS(ios => + { + ios.FinishedLaunching( + (_, _) => + { + OneSignalNotificationDelegate.Install(); + InitializeOneSignal(appId); + return true; + } + ); + }); + }); +#endif + var app = builder.Build(); + + OneSignal.Debug.LogLevel = OsLogLevel.VERBOSE; OneSignal.Notifications.WillDisplay += (s, e) => System.Diagnostics.Debug.WriteLine("OneSignal notification will display"); OneSignal.Notifications.Clicked += (s, e) => System.Diagnostics.Debug.WriteLine("OneSignal notification clicked"); +#if !IOS + InitializeOneSignal(appId); +#endif + return app; } + + private static void InitializeOneSignal(string appId) + { + OneSignal.Initialize(appId); + } } diff --git a/examples/plugin-local-notif/Platforms/iOS/AppDelegate.cs b/examples/plugin-local-notif/Platforms/iOS/AppDelegate.cs index 1bab4715..6479ab56 100644 --- a/examples/plugin-local-notif/Platforms/iOS/AppDelegate.cs +++ b/examples/plugin-local-notif/Platforms/iOS/AppDelegate.cs @@ -1,6 +1,8 @@ using Foundation; using Microsoft.Maui; using Microsoft.Maui.Hosting; +using OneSignalSDK.DotNet.iOS; +using UIKit; namespace PluginLocalNotifDemo; @@ -8,4 +10,26 @@ namespace PluginLocalNotifDemo; public class AppDelegate : MauiUIApplicationDelegate { protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + + [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] + public void DidRegisterForRemoteNotifications(UIApplication application, NSData deviceToken) + { + NotificationsManualIntegration.DidRegisterForRemoteNotifications(deviceToken); + } + + [Export("application:didFailToRegisterForRemoteNotificationsWithError:")] + public void DidFailToRegisterForRemoteNotifications(UIApplication application, NSError error) + { + NotificationsManualIntegration.DidFailToRegisterForRemoteNotifications(error); + } + + [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")] + public void DidReceiveRemoteNotification( + UIApplication application, + NSDictionary userInfo, + Action completionHandler + ) + { + NotificationsManualIntegration.DidReceiveRemoteNotification(userInfo, completionHandler); + } } diff --git a/examples/plugin-local-notif/Platforms/iOS/Info.plist b/examples/plugin-local-notif/Platforms/iOS/Info.plist index b7085901..4ea76b3c 100644 --- a/examples/plugin-local-notif/Platforms/iOS/Info.plist +++ b/examples/plugin-local-notif/Platforms/iOS/Info.plist @@ -36,6 +36,8 @@ remote-notification + OneSignal_disable_swizzling + aps-environment development diff --git a/examples/plugin-local-notif/Platforms/iOS/OneSignalNotificationDelegate.cs b/examples/plugin-local-notif/Platforms/iOS/OneSignalNotificationDelegate.cs new file mode 100644 index 00000000..de109f1a --- /dev/null +++ b/examples/plugin-local-notif/Platforms/iOS/OneSignalNotificationDelegate.cs @@ -0,0 +1,79 @@ +using OneSignalSDK.DotNet.iOS; +using UserNotifications; + +namespace PluginLocalNotifDemo; + +internal sealed class OneSignalNotificationDelegate : UNUserNotificationCenterDelegate +{ + private static OneSignalNotificationDelegate? _instance; + private readonly IUNUserNotificationCenterDelegate _localNotificationDelegate; + + private OneSignalNotificationDelegate( + IUNUserNotificationCenterDelegate localNotificationDelegate + ) + { + _localNotificationDelegate = localNotificationDelegate; + } + + public static void Install() + { + var notificationCenter = UNUserNotificationCenter.Current; + var localNotificationDelegate = + notificationCenter.Delegate + ?? throw new InvalidOperationException( + "Plugin.LocalNotification must install its iOS delegate before OneSignal." + ); + + _instance = new OneSignalNotificationDelegate(localNotificationDelegate); + notificationCenter.Delegate = _instance; + } + + public override void WillPresentNotification( + UNUserNotificationCenter center, + UNNotification notification, + Action completionHandler + ) + { + var presentationOptions = + UNNotificationPresentationOptions.Banner + | UNNotificationPresentationOptions.List + | UNNotificationPresentationOptions.Sound + | UNNotificationPresentationOptions.Badge; + + if ( + NotificationsManualIntegration.TryHandleWillPresentNotification( + notification, + presentationOptions, + completionHandler + ) + ) + { + return; + } + + _localNotificationDelegate.WillPresentNotification(center, notification, completionHandler); + } + + public override void DidReceiveNotificationResponse( + UNUserNotificationCenter center, + UNNotificationResponse response, + Action completionHandler + ) + { + if ( + NotificationsManualIntegration.TryHandleNotificationResponse( + response, + completionHandler + ) + ) + { + return; + } + + _localNotificationDelegate.DidReceiveNotificationResponse( + center, + response, + completionHandler + ); + } +} diff --git a/examples/plugin-local-notif/README.md b/examples/plugin-local-notif/README.md index bc7a0194..b5a02819 100644 --- a/examples/plugin-local-notif/README.md +++ b/examples/plugin-local-notif/README.md @@ -1,29 +1,10 @@ -# OneSignal + Plugin.LocalNotification Repro +# OneSignal + Plugin.LocalNotification -Minimal .NET MAUI app for investigating the iOS interaction reported in -[GitHub issue #132](https://github.com/OneSignal/OneSignal-DotNet-SDK/issues/132). - -## What This Reproduces - -Issue #132 reports an iOS crash when `OneSignalSDK.DotNet` and -`Plugin.LocalNotification` are both installed: - -```text -Cannot get the method descriptor for the selector -'onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:' -on the type 'Plugin.LocalNotification.Platforms.UserNotificationCenterDelegate' -``` - -Related issue #110 reported the same failure shape for the foreground delivery -selector: - -```text -onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler: -``` - -Both failures point at the same interaction: OneSignal's native iOS SDK swizzles -`UNUserNotificationCenterDelegate` methods, while `Plugin.LocalNotification` -installs its own delegate that only implements the normal Apple selectors. +This .NET MAUI app demonstrates the supported iOS manual-integration path for +using OneSignal with `Plugin.LocalNotification`. It avoids the managed delegate +selector crash reported in +[GitHub issue #132](https://github.com/OneSignal/OneSignal-DotNet-SDK/issues/132) +without relying on private selectors or Objective-C runtime patches. ## Run @@ -43,23 +24,41 @@ Or run Android: The shared scripts select from currently booted simulators or connected Android devices. -## Repro Steps +## iOS Manual Integration -1. Launch the app on iOS. -2. Tap `Request OneSignal Permission`. -3. Tap `Request LocalNotification Permission`. -4. Tap `Show Local Notification` while the app is foregrounded. -5. If the notification is delivered, tap it from Notification Center. -6. Watch device logs for the `onesignalUserNotificationCenter:*` selector crash. +The integration has four required parts: -The bundled app ID matches the main demo app's default ID when -`ONESIGNAL_APP_ID` is missing or still set to the placeholder. To test against a -different OneSignal app, set `ONESIGNAL_APP_ID` in `.env`. +1. `Info.plist` sets `OneSignal_disable_swizzling` to `true`. +2. `OneSignalNotificationDelegate` captures the delegate installed by + `Plugin.LocalNotification` and becomes the app's notification-center + delegate. +3. `MauiProgram` installs that coordinator after the plugin's + `FinishedLaunching` callback, then initializes OneSignal. +4. `AppDelegate` forwards APNs registration, registration failure, and + background remote-notification callbacks through + `NotificationsManualIntegration`. + +The coordinator routes OneSignal foreground and tap callbacks to +`NotificationsManualIntegration`. Non-OneSignal notifications are forwarded to +the plugin delegate. Each Apple completion handler has one owner: + +- OneSignal owns background remote-notification completion. +- The manual integration completes OneSignal foreground and tap callbacks. +- `Plugin.LocalNotification` completes callbacks for local notifications. -## Notes +The app must retain ownership of `UNUserNotificationCenter.Current.Delegate`. +If another library replaces the coordinator after startup, forwarding stops. +Install any library-owned delegate first, wrap it with the coordinator, and +initialize OneSignal last. -The native OneSignal iOS SDK now documents disabling swizzling via -`OneSignal_disable_swizzling` and manually forwarding notification delegate -methods. This .NET binding currently does not expose the newer manual forwarding -APIs, so this sample keeps swizzling enabled and demonstrates the reported -interaction directly. +## Verify + +On iOS, verify local foreground display and tap, OneSignal foreground +`WillDisplay` and banner presentation, background and silent delivery, +OneSignal `Clicked`, and APNs token registration. No +`onesignalUserNotificationCenter:*` or +`oneSignalDidRegisterForRemoteNotifications:*` registrar crash should occur. + +The bundled app ID matches the main demo app's default ID when +`ONESIGNAL_APP_ID` is missing or still set to the placeholder. Set +`ONESIGNAL_APP_ID` in `.env` to use a different OneSignal app. diff --git a/examples/plugin-local-notif/run-ios.sh b/examples/plugin-local-notif/run-ios.sh index 80f7cb5e..6dbd1a19 100755 --- a/examples/plugin-local-notif/run-ios.sh +++ b/examples/plugin-local-notif/run-ios.sh @@ -2,4 +2,5 @@ set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +dotnet clean "$SCRIPT_DIR/plugin-local-notif.csproj" -f net10.0-ios "$SCRIPT_DIR/../run-ios.sh" "$SCRIPT_DIR/plugin-local-notif.csproj" From bc50f512c33b759cf162a29919c3523853e02cea Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 16 Jul 2026 15:00:56 -0700 Subject: [PATCH 2/3] chore: [SDK-4788] improve app ID validation --- examples/plugin-local-notif/.env.example | 1 - examples/plugin-local-notif/DotEnv.cs | 5 ++ examples/plugin-local-notif/MainPage.cs | 73 ++++++++++++++++++++-- examples/plugin-local-notif/MauiProgram.cs | 32 ++++++---- examples/plugin-local-notif/README.md | 10 +-- 5 files changed, 99 insertions(+), 22 deletions(-) diff --git a/examples/plugin-local-notif/.env.example b/examples/plugin-local-notif/.env.example index 96620213..2c398f31 100644 --- a/examples/plugin-local-notif/.env.example +++ b/examples/plugin-local-notif/.env.example @@ -1,2 +1 @@ -# Default App ID (used when ONESIGNAL_APP_ID is empty or missing): 77e32082-ea27-42e3-a898-c72e141824ef ONESIGNAL_APP_ID=your-onesignal-app-id diff --git a/examples/plugin-local-notif/DotEnv.cs b/examples/plugin-local-notif/DotEnv.cs index 3ff7262c..897a20b9 100644 --- a/examples/plugin-local-notif/DotEnv.cs +++ b/examples/plugin-local-notif/DotEnv.cs @@ -2,9 +2,14 @@ namespace PluginLocalNotifDemo; public static class DotEnv { + private const string OneSignalAppIdPlaceholder = "your-onesignal-app-id"; private static readonly Dictionary Values = new(); private static bool _loaded; + public static string OneSignalAppId => Get("ONESIGNAL_APP_ID").Trim(); + public static bool HasOneSignalAppId => + !string.IsNullOrWhiteSpace(OneSignalAppId) && OneSignalAppId != OneSignalAppIdPlaceholder; + public static void Load() { if (_loaded) diff --git a/examples/plugin-local-notif/MainPage.cs b/examples/plugin-local-notif/MainPage.cs index b3c2dea0..f6c89101 100644 --- a/examples/plugin-local-notif/MainPage.cs +++ b/examples/plugin-local-notif/MainPage.cs @@ -1,3 +1,5 @@ +using System.Text; +using System.Text.Json; using OneSignalSDK.DotNet; using OneSignalSDK.DotNet.Core.Notifications; using OneSignalSDK.DotNet.Core.User; @@ -88,6 +90,24 @@ public MainPage() ); }; + var showOneSignalNotificationButton = new Button + { + Text = "Show OneSignal Notification", + AutomationId = "show_onesignal_notification_button", + }; + showOneSignalNotificationButton.Clicked += async (s, e) => + { + showOneSignalNotificationButton.IsEnabled = false; + try + { + await SendSimpleOneSignalNotificationAsync(); + } + finally + { + showOneSignalNotificationButton.IsEnabled = true; + } + }; + var clearButton = new Button { Text = "Clear Delivered Notifications", @@ -133,6 +153,7 @@ public MainPage() requestOneSignalPermissionButton, requestLocalPermissionButton, showLocalNotificationButton, + showOneSignalNotificationButton, clearButton, refreshPushInfoButton, _statusLabel, @@ -160,10 +181,9 @@ private void RefreshPushInfoLabel() { var pushSubscription = OneSignal.User.PushSubscription; _pushInfoLabel.Text = - $"OneSignal ID: {FormatValue(OneSignal.User.OneSignalId)}\n" - + $"Push subscription ID: {FormatValue(pushSubscription.Id)}\n" - + $"Push opted in: {pushSubscription.OptedIn}\n" - + $"Push token: {FormatValue(pushSubscription.Token)}"; + $"OneSignal ID:\n{FormatValue(OneSignal.User.OneSignalId)}\n" + + $"Push subscription ID:\n{FormatValue(pushSubscription.Id)}\n" + + $"Push opted in: {pushSubscription.OptedIn}"; } private void OnPermissionChanged(object? sender, NotificationPermissionChangedEventArgs args) @@ -185,6 +205,51 @@ private void OnUserChanged(object? sender, UserStateChangedEventArgs args) MainThread.BeginInvokeOnMainThread(RefreshPushInfoLabel); } + private async Task SendSimpleOneSignalNotificationAsync() + { + if (!DotEnv.HasOneSignalAppId) + { + SetStatus("Set ONESIGNAL_APP_ID in .env before sending a OneSignal notification."); + return; + } + + var pushSubscriptionId = OneSignal.User.PushSubscription.Id; + if (string.IsNullOrWhiteSpace(pushSubscriptionId)) + { + SetStatus( + "No OneSignal push subscription ID yet. Refresh after permission is granted." + ); + return; + } + + using var client = new HttpClient(); + client.DefaultRequestHeaders.Add("Accept", "application/vnd.onesignal.v1+json"); + + var payload = new Dictionary + { + ["app_id"] = DotEnv.OneSignalAppId, + ["headings"] = new Dictionary { ["en"] = "Simple Notification" }, + ["contents"] = new Dictionary + { + ["en"] = "This is a simple push notification", + }, + ["include_subscription_ids"] = new[] { pushSubscriptionId }, + }; + + var json = JsonSerializer.Serialize(payload); + var response = await client.PostAsync( + "https://onesignal.com/api/v1/notifications", + new StringContent(json, Encoding.UTF8, "application/json") + ); + var responseJson = await response.Content.ReadAsStringAsync(); + + SetStatus( + response.IsSuccessStatusCode + ? $"OneSignal notification requested: {responseJson}" + : $"OneSignal notification failed: {responseJson}" + ); + } + private void SetStatus(string message) { MainThread.BeginInvokeOnMainThread(() => diff --git a/examples/plugin-local-notif/MauiProgram.cs b/examples/plugin-local-notif/MauiProgram.cs index 7816a4bf..d3e4eb00 100644 --- a/examples/plugin-local-notif/MauiProgram.cs +++ b/examples/plugin-local-notif/MauiProgram.cs @@ -9,18 +9,10 @@ namespace PluginLocalNotifDemo; public static class MauiProgram { - private const string DefaultAppId = "77e32082-ea27-42e3-a898-c72e141824ef"; - public static MauiApp CreateMauiApp() { DotEnv.Load(); - var envAppId = DotEnv.Get("ONESIGNAL_APP_ID"); - var appId = - string.IsNullOrWhiteSpace(envAppId) || envAppId == "your-onesignal-app-id" - ? DefaultAppId - : envAppId.Trim(); - var builder = MauiApp.CreateBuilder(); builder.UseMauiApp().UseLocalNotification(); @@ -33,8 +25,7 @@ public static MauiApp CreateMauiApp() ios.FinishedLaunching( (_, _) => { - OneSignalNotificationDelegate.Install(); - InitializeOneSignal(appId); + InitializeOneSignal(installNotificationDelegate: true); return true; } ); @@ -51,14 +42,29 @@ public static MauiApp CreateMauiApp() System.Diagnostics.Debug.WriteLine("OneSignal notification clicked"); #if !IOS - InitializeOneSignal(appId); + InitializeOneSignal(); #endif return app; } - private static void InitializeOneSignal(string appId) + private static void InitializeOneSignal(bool installNotificationDelegate = false) { - OneSignal.Initialize(appId); + if (!DotEnv.HasOneSignalAppId) + { + System.Diagnostics.Debug.WriteLine( + "Set ONESIGNAL_APP_ID in .env to initialize OneSignal." + ); + return; + } + +#if IOS + if (installNotificationDelegate) + { + OneSignalNotificationDelegate.Install(); + } +#endif + + OneSignal.Initialize(DotEnv.OneSignalAppId); } } diff --git a/examples/plugin-local-notif/README.md b/examples/plugin-local-notif/README.md index b5a02819..6f7db855 100644 --- a/examples/plugin-local-notif/README.md +++ b/examples/plugin-local-notif/README.md @@ -12,6 +12,7 @@ From this directory, run iOS: ```sh cp .env.example .env +# Set ONESIGNAL_APP_ID in .env before launching. ./run-ios.sh ``` @@ -55,10 +56,11 @@ initialize OneSignal last. On iOS, verify local foreground display and tap, OneSignal foreground `WillDisplay` and banner presentation, background and silent delivery, -OneSignal `Clicked`, and APNs token registration. No +OneSignal `Clicked`, and APNs token registration. The +`Show OneSignal Notification` button sends a push to the displayed subscription +ID so these paths can be tested without opening the OneSignal dashboard. No `onesignalUserNotificationCenter:*` or `oneSignalDidRegisterForRemoteNotifications:*` registrar crash should occur. -The bundled app ID matches the main demo app's default ID when -`ONESIGNAL_APP_ID` is missing or still set to the placeholder. Set -`ONESIGNAL_APP_ID` in `.env` to use a different OneSignal app. +Set `ONESIGNAL_APP_ID` in `.env` before running the sample. The app does not +fall back to a built-in OneSignal app ID. From ae4f13f8cc46aba5314ca7f15fd53d6595c4fd16 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Fri, 17 Jul 2026 10:50:08 -0700 Subject: [PATCH 3/3] chore: [SDK-4788] log notification events to console Co-authored-by: Cursor --- examples/plugin-local-notif/MauiProgram.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/plugin-local-notif/MauiProgram.cs b/examples/plugin-local-notif/MauiProgram.cs index d3e4eb00..99931c90 100644 --- a/examples/plugin-local-notif/MauiProgram.cs +++ b/examples/plugin-local-notif/MauiProgram.cs @@ -37,9 +37,9 @@ public static MauiApp CreateMauiApp() OneSignal.Debug.LogLevel = OsLogLevel.VERBOSE; OneSignal.Notifications.WillDisplay += (s, e) => - System.Diagnostics.Debug.WriteLine("OneSignal notification will display"); + Console.WriteLine("OneSignal notification will display"); OneSignal.Notifications.Clicked += (s, e) => - System.Diagnostics.Debug.WriteLine("OneSignal notification clicked"); + Console.WriteLine("OneSignal notification clicked"); #if !IOS InitializeOneSignal();