Usage
Schedule local notifications and implement push notifications for player engagement
Essential Kit wraps native iOS (Apple Push Notification service, or APNs) and Android (Firebase Cloud Messaging, or FCM) notification APIs into a single Unity interface. Essential Kit auto-initializes NotificationServices - you can start scheduling notifications immediately after requesting permissions.
Table of Contents
Understanding Core Concepts
Before scheduling notifications, understand the two distinct notification types and when to use each.
Local vs Push Notifications
Local Notifications are scheduled on the device and fire even when your app isn't running. Perfect for game-driven events.
Examples:
Energy refill timers (30 minutes after energy depletes)
Daily reward reminders (every day at 6 PM)
Building completion timers (when construction finishes)
Recurring challenges (weekly tournament reminders)
Key characteristics:
Work completely offline - no server required
Scheduled using time intervals or calendar dates
Stored on device until fired or cancelled
Limited to device-scheduled triggers only
Push Notifications are sent from your game server to devices in real-time. Perfect for server-driven events.
Examples:
Tournament results and leaderboard updates
Flash sale announcements
Friend requests and social interactions
Server maintenance notices
Key characteristics:
Require server infrastructure (Firebase, OneSignal, or custom backend)
Can target specific users or broadcast to all players
Sent in real-time based on server events
Can carry custom data for deep linking
Notification Triggers
Local notifications fire based on triggers you specify when scheduling:
Time Interval Triggers fire after a specific delay from scheduling. Use for relative timing like cooldowns and energy refills.
// Fire 30 minutes from now
.SetTimeIntervalNotificationTrigger(1800) // seconds
// Fire every hour repeatedly
.SetTimeIntervalNotificationTrigger(3600, repeats: true)Calendar Triggers fire at specific clock times. Use for daily rewards, weekly events, and scheduled content.
// Fire every day at 6 PM
var components = new DateComponents();
components.Hour = 18;
components.Minute = 0;
.SetCalendarNotificationTrigger(components, repeats: true)Notification Lifecycle
Creation: Build notification with NotificationBuilder specifying ID, title, body, and trigger
Scheduling: Call
ScheduleNotification()to register with systemDelivery: System fires notification at trigger time (or immediately for push notifications)
User Interaction: User taps notification to open app, or dismisses it
Handling: Your app receives
OnNotificationReceivedevent with notification data
Import Namespaces
using System;
using System.Collections.Generic;
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;
using VoxelBusters.CoreLibrary.NativePlugins;Include System namespaces for DateTime and Dictionary which are commonly used with notification scheduling and custom data.
Event Registration
Register for notification events in OnEnable and unregister in OnDisable to receive notifications and permission updates:
void OnEnable()
{
NotificationServices.OnNotificationReceived += OnNotificationReceived;
NotificationServices.OnSettingsUpdate += OnSettingsUpdate;
}
void OnDisable()
{
NotificationServices.OnNotificationReceived -= OnNotificationReceived;
NotificationServices.OnSettingsUpdate -= OnSettingsUpdate;
}OnNotificationReceived
When local or push notification is delivered (foreground or background), or when user taps notification to open app
OnSettingsUpdate
When user changes notification permissions in device settings
Essential Kit automatically delays delivering launch notifications until you register for OnNotificationReceived. This prevents missing notifications that arrived before your code was ready to handle them.
How Permissions Work
Just call RequestPermission() directly - no permission checks needed beforehand. On the first call, Essential Kit automatically shows the system permission dialog. If permission is already granted, it completes immediately. When the user denies access, result.PermissionStatus becomes Denied while the error argument stays null.
void RequestNotificationPermissions()
{
var options = NotificationPermissionOptions.Alert |
NotificationPermissionOptions.Sound |
NotificationPermissionOptions.Badge;
NotificationServices.RequestPermission(options, callback: (result, error) =>
{
if (error != null)
{
Debug.LogError($"Permission request failed: {error.Description}");
return;
}
Debug.Log($"Permission status: {result.PermissionStatus}");
if (result.PermissionStatus == NotificationPermissionStatus.Authorized)
{
Debug.Log("Notifications enabled - ready to schedule");
}
else if (result.PermissionStatus == NotificationPermissionStatus.Denied)
{
Debug.LogWarning("User denied notification permissions");
}
});
}UX best practice: Show a custom explanation screen before requesting permissions. Explain benefits like "Get notified when your energy refills" to improve approval rates. iOS users especially appreciate understanding value before seeing system dialogs.
Handling Permission Denial
Handle permission issues in the error callback or by checking the permission status:
void OnPermissionResult(NotificationServicesRequestPermissionResult result, Error error)
{
if (error != null)
{
Debug.LogError($"Permission request failed: {error.Description}");
return;
}
if (result.PermissionStatus == NotificationPermissionStatus.Denied)
{
Debug.Log("User denied notification permissions");
// Guide user to settings if they want to re-enable
// Utilities.OpenApplicationSettings();
return;
}
// Permission granted - can now schedule notifications
}Optional: Check Permission Status
Use GetSettings() only when you need to inspect the exact state before calling the main operation, or to customize UI messaging:
NotificationServices.GetSettings((result) =>
{
Debug.Log($"Permission status: {result.Settings.PermissionStatus}");
if (result.Settings.PermissionStatus == NotificationPermissionStatus.NotDetermined)
{
Debug.Log("Permission not requested yet");
}
else if (result.Settings.PermissionStatus == NotificationPermissionStatus.Authorized)
{
Debug.Log("Permission granted - can schedule notifications");
}
});Permission status values:
NotDetermined: Permission never requested (iOS) or not applicable (Android)Denied: User explicitly denied permissionAuthorized: User granted full notification permissionsProvisional: Limited permissions granted (iOS 12+ quiet notifications)
Local Notifications
Creating Notifications
Build notifications using NotificationBuilder with a unique ID, title, body, and trigger:
INotification CreateEnergyRefillNotification()
{
return NotificationBuilder.CreateNotification("energy_refill")
.SetTitle("Energy Restored!")
.SetBody("Your energy is full. Come back and continue playing!")
.SetBadge(1)
.SetTimeIntervalNotificationTrigger(1800) // 30 minutes in seconds
.Create();
}Notification IDs must be unique. Scheduling a notification with an existing ID replaces the previous notification. Use descriptive IDs like "energy_refill" or "daily_reward" for easier management.
Scheduling Notifications
Schedule the notification to be delivered at the trigger time:
void ScheduleEnergyNotification()
{
INotification notification = CreateEnergyRefillNotification();
NotificationServices.ScheduleNotification(notification, (success, error) =>
{
if (success)
{
Debug.Log("Energy refill notification scheduled for 30 minutes");
}
else
{
Debug.LogError($"Failed to schedule: {error?.Description}");
}
});
}Notification Triggers
Time Interval Triggers
Use when timing is relative to the current moment:
// Fire once after 1 hour
.SetTimeIntervalNotificationTrigger(3600)
// Fire every 24 hours (daily)
.SetTimeIntervalNotificationTrigger(86400, repeats: true)
// Fire once after 30 minutes
.SetTimeIntervalNotificationTrigger(TimeSpan.FromMinutes(30).TotalSeconds)Common patterns:
Energy refills: 30-60 minutes after depletion
Lives regeneration: Time until next life available
Building timers: Construction completion time
Temporary boosts: When boost expires
Calendar Triggers
Use when timing should match specific clock times:
// Fire every day at 6 PM
var dailyComponents = new DateComponents();
dailyComponents.Hour = 18;
dailyComponents.Minute = 0;
var notification = NotificationBuilder.CreateNotification("daily_reward")
.SetTitle("Daily Reward Available!")
.SetBody("Claim your free daily gems and bonuses!")
.SetCalendarNotificationTrigger(dailyComponents, repeats: true)
.Create();Common patterns:
// Daily notification at specific time
var daily = new DateComponents();
daily.Hour = 18; // 6 PM
daily.Minute = 0;
// Weekly notification (Monday at 9 AM)
var weekly = new DateComponents();
weekly.DayOfWeek = 1; // Monday (ISO 8601: 1=Monday, 7=Sunday)
weekly.Hour = 9;
weekly.Minute = 0;
// Monthly notification (15th of each month at noon)
var monthly = new DateComponents();
monthly.Day = 15;
monthly.Hour = 12;
monthly.Minute = 0;
// Annual notification (birthday, anniversary)
var annual = new DateComponents();
annual.Month = userBirthday.Month;
annual.Day = userBirthday.Day;
annual.Hour = 9;
annual.Minute = 0;Adding Custom Data
Attach custom data to notifications for deep linking or identifying notification types:
var notification = NotificationBuilder.CreateNotification("tournament_start")
.SetTitle("Tournament Starting Soon!")
.SetBody("PvP Tournament begins in 1 hour. Prepare for battle!")
.SetUserInfo(new Dictionary<string, string>
{
["type"] = "tournament_reminder",
["tournament_id"] = "pvp_2024_10",
["deep_link"] = "mygame://tournaments/pvp_2024_10"
})
.SetTimeIntervalNotificationTrigger(3600)
.Create();Access custom data in your event handler:
void OnNotificationReceived(NotificationServicesNotificationReceivedResult data)
{
var notification = data.Notification;
if (notification.UserInfo is IDictionary<string, string> userInfo)
{
if (userInfo.TryGetValue("type", out string notifType))
{
Debug.Log($"Notification type: {notifType}");
if (notifType == "tournament_reminder" &&
userInfo.TryGetValue("tournament_id", out string tournamentId))
{
// Navigate to tournament
Debug.Log($"Open tournament screen for {tournamentId}.");
}
}
}
}Managing Scheduled Notifications
Get Scheduled Notifications
Retrieve all pending notifications that haven't been delivered yet:
NotificationServices.GetScheduledNotifications((result, error) =>
{
if (error == null)
{
INotification[] notifications = result.Notifications;
Debug.Log($"Total scheduled: {notifications.Length}");
foreach (var notification in notifications)
{
Debug.Log($"{notification.Id}: {notification.Title}");
}
}
});Cancel Scheduled Notifications
// Cancel specific notification by ID
NotificationServices.CancelScheduledNotification("energy_refill");
// Or cancel by notification object
NotificationServices.CancelScheduledNotification(notification);
// Cancel all scheduled notifications
NotificationServices.CancelAllScheduledNotifications();Common patterns:
// Cancel notification when player returns to game
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
// Player opened app - cancel energy refill notification
NotificationServices.CancelScheduledNotification("energy_refill");
// Update energy notification based on current state
if (!PlayerEnergy.IsFull())
{
ScheduleEnergyNotification();
}
}
}Delivered Notifications
Get Delivered Notifications
Retrieve notifications currently visible in the device notification center:
NotificationServices.GetDeliveredNotifications((result, error) =>
{
if (error == null)
{
INotification[] delivered = result.Notifications;
Debug.Log($"Notifications in notification center: {delivered.Length}");
}
});Clear Delivered Notifications
// Clear all notifications from notification center when player opens app
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
NotificationServices.RemoveAllDeliveredNotifications();
Debug.Log("Cleared notification center");
}
}Push Notifications
Push notifications require server infrastructure to send messages. Essential Kit handles device registration and notification reception.
Understanding Push Notification Flow
Player App ──▶ (1) Register & fetch device token
▽
Essential Kit caches token
▽
Game Server ─▶ (2) Send payload to APNs/FCM
▽
Apple/Google push services ─▶ (3) Deliver to device
▽
Player App ──▶ (4) `OnNotificationReceived`Device registration: Your app requests a push notification device token.
Token storage: Send the token to your game server (Essential Kit also caches it locally).
Server send: Your server forwards the payload to APNs (iOS) or FCM (Android).
Platform delivery: Apple or Google push gateways route the message to the device.
App receives: Essential Kit surfaces the payload via
OnNotificationReceived.
Registering for Push Notifications
Request device token after user grants notification permissions:
void RegisterForPushNotifications()
{
NotificationServices.RegisterForPushNotifications((result, error) =>
{
if (error == null)
{
string deviceToken = result.DeviceToken;
Debug.Log($"Device token: {deviceToken}");
// Send token to your game server for push messaging
SendTokenToServer(deviceToken);
}
else
{
Debug.LogError($"Push registration failed: {error.Description}");
}
});
}
void SendTokenToServer(string deviceToken)
{
// Send to your backend API
Debug.Log($"Sending device token to server: {deviceToken}");
// Example: POST to your server
// Include player ID, platform, and device token
}Essential Kit also provides helpers when you want the plugin to manage re-registration or expose a global callback:
// Automatically registers again once permissions are confirmed
NotificationServices.TryRegisterForPushNotifications();
// Subscribe once to get notified whenever a token refresh completes
NotificationServices.OnRegisterForPushNotificationsComplete += (result, error) =>
{
if (error == null)
{
Debug.Log($"Push token updated: {result.DeviceToken}");
}
};Device token caching: Essential Kit caches the device token after successful registration. Access it anytime with NotificationServices.CachedSettings.DeviceToken without making another registration request.
Platform-Specific Token Formats
iOS: Returns APNS device token (send to your APNS server or convert to FCM token)
Android: Returns FCM registration token (send directly to FCM)
For unified backends, use FCM SDK to convert APNS tokens to FCM tokens on iOS, allowing a single FCM endpoint for both platforms.
Checking Registration Status
void CheckPushRegistration()
{
bool isRegistered = NotificationServices.IsRegisteredForPushNotifications();
Debug.Log($"Registered for push: {isRegistered}");
if (isRegistered)
{
string cachedToken = NotificationServices.CachedSettings.DeviceToken;
Debug.Log($"Cached device token: {cachedToken}");
}
}Unregistering from Push Notifications
void UnregisterFromPush()
{
NotificationServices.UnregisterForPushNotifications();
Debug.Log("Unregistered from push notifications");
}Handling Push Notification Reception
Push notifications are delivered through the same OnNotificationReceived event as local notifications:
void OnNotificationReceived(NotificationServicesNotificationReceivedResult data)
{
var notification = data.Notification;
Debug.Log($"Received: {notification.Title} - {notification.Body}");
Debug.Log($"Is launch notification: {notification.IsLaunchNotification}");
// Check if app was launched from notification tap
if (notification.IsLaunchNotification)
{
Debug.Log("App opened from notification tap");
Debug.Log("Navigate player based on launch notification.");
}
// Process custom data from server
if (notification.UserInfo != null)
{
Debug.Log("Push notification custom data received");
Debug.Log($"Custom payload: {notification.UserInfo}");
}
// Clear badge after handling notification
NotificationServices.SetApplicationIconBadgeNumber(0);
}Server Payload Examples
Reference payloads for testing push notifications from your server:
Android (FCM) Payload:
{
"to": "device_token_here",
"data": {
"content_title": "Flash Sale!",
"content_text": "50% off gem packs for the next 2 hours!",
"ticker_text": "Limited time offer",
"tag": "flash_sale",
"badge": 1,
"user_info": {
"offer_id": "gems_50_off",
"expires": "2024-10-06T10:00:00Z"
}
}
}iOS (APNS) Payload:
{
"aps": {
"alert": {
"title": "Flash Sale!",
"body": "50% off gem packs for the next 2 hours!"
},
"badge": 1,
"sound": "default"
},
"user_info": {
"offer_id": "gems_50_off",
"expires": "2024-10-06T10:00:00Z"
}
}Badge Management
Update the badge number on your app icon to show notification counts or pending items:
// Set badge number
NotificationServices.SetApplicationIconBadgeNumber(5);
// Clear badge
NotificationServices.SetApplicationIconBadgeNumber(0);Common patterns:
// Clear badge when app gains focus
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
NotificationServices.SetApplicationIconBadgeNumber(0);
}
}
// Increment badge when scheduling notification
var notification = NotificationBuilder.CreateNotification("daily_reward")
.SetTitle("Daily Reward Available!")
.SetBadge(1) // Show badge count in notification
.Create();Badge numbers require Badge permission in RequestPermission(). On iOS, badge may not be visible if user disabled badges in device settings. Badge permission is granted independently of alert/sound permissions.
Data Properties
NotificationServicesNotificationReceivedResult.Notification
INotification
Gives you the fully-populated notification instance (title, body, payload, trigger) whenever OnNotificationReceived fires.
INotification.UserInfo
IDictionary
Custom key/value payload from local builders or remote push messages—use it for deep links and contextual routing.
INotification.IsLaunchNotification
bool
Identifies whether the notification launched or re-activated the app so you can branch onboarding flows.
NotificationSettings.PermissionStatus
NotificationPermissionStatus
Reflects the latest permission choice (Authorized, Denied, etc.) returned by GetSettings and cached on NotificationServices.CachedSettings.
NotificationSettings.DeviceToken
string
The most recent device token (APNs/FCM). Send it to your backend whenever you register for push notifications.
NotificationSettings.PushNotificationEnabled
bool
Confirms whether the platform currently allows remote notifications—handy before prompting users to re-enable permissions.
Core APIs Reference
NotificationServices.RequestPermission(options, callback)
Request notification permissions (Alert, Sound, Badge)
Result via callback with PermissionStatus or error
NotificationBuilder.CreateNotification(id)
Start building a notification with unique identifier
NotificationBuilder instance for method chaining
NotificationServices.ScheduleNotification(notification, callback)
Schedule local notification for delivery
Success flag via callback
NotificationServices.GetScheduledNotifications(callback)
Get all pending scheduled notifications
Array of INotification via callback
NotificationServices.CancelScheduledNotification(id)
Cancel specific scheduled notification by ID
No return value
NotificationServices.CancelAllScheduledNotifications()
Cancel all pending scheduled notifications
No return value
NotificationServices.GetDeliveredNotifications(callback)
Get notifications currently in notification center
Array of INotification via callback
NotificationServices.RemoveAllDeliveredNotifications()
Clear all notifications from notification center
No return value
NotificationServices.RegisterForPushNotifications(callback)
Register device for push notifications
Device token via callback
NotificationServices.UnregisterForPushNotifications()
Unregister from push notifications
No return value
NotificationServices.IsRegisteredForPushNotifications()
Check if device is registered for push
bool - true if registered
NotificationServices.SetApplicationIconBadgeNumber(count)
Set app icon badge number (0 to clear)
No return value
NotificationServices.GetSettings(callback)
Optional: Get current permission status and settings
NotificationSettings via callback
NotificationServices.CachedSettings
Access cached notification settings without callback
NotificationSettings object
NotificationBuilder Methods
Chain these methods when building notifications:
SetTitle(title)
Set notification title text
SetSubtitle(subtitle)
Set notification subtitle (iOS only)
SetBody(body)
Set notification body message
SetBadge(number)
Set app icon badge number
SetUserInfo(dictionary)
Set custom data dictionary
SetPriority(priority)
Set notification priority (Low/Medium/High/Max)
SetTimeIntervalNotificationTrigger(seconds, repeats)
Set time-based trigger (seconds from now)
SetCalendarNotificationTrigger(components, repeats)
Set calendar-based trigger (specific times/dates)
SetSoundFileName(filename)
Set custom sound file from StreamingAssets
SetAndroidProperties(properties)
Set Android-specific properties (channels, icons)
SetIosProperties(properties)
Set iOS-specific properties (thread ID, attachments)
Create()
Build and return final INotification object
Error Handling
PermissionNotAvailable
User declined notification permissions
Show explanation of benefits and guide to Utilities.OpenApplicationSettings()
TriggerNotValid
Missing or incompatible trigger configuration
Ensure the trigger type matches the platform requirements
ConfigurationError
Notification payload missing required data
Ensure title/body/payload are populated before scheduling
ScheduledTimeNotValid
Scheduled time already elapsed or invalid
Validate the fire time before submitting the request
Unknown
Platform-specific error occurred
Log error details and retry or contact support
Error handling pattern:
NotificationServices.ScheduleNotification(notification, (success, error) =>
{
if (success)
{
Debug.Log("Notification scheduled successfully");
return;
}
// Handle error
if (error != null)
{
Debug.LogError($"Failed to schedule: {error.Description}");
// Check specific error codes
if (error.Code == (int)NotificationServicesErrorCode.PermissionNotAvailable)
{
// Guide user to re-enable permissions
Debug.LogWarning("Explain why notifications are needed and direct players to Settings.");
}
}
});Advanced: Manual Initialization
Advanced users only: Essential Kit auto-initializes NotificationServices with settings from Essential Kit Settings. Only use manual initialization for runtime configuration changes, feature flags, or server-driven settings.
Understanding Auto-Initialization
Default behavior (no action required):
Essential Kit automatically initializes NotificationServices before scene loads
Uses settings from
Resources/EssentialKitSettings.assetNotification system is immediately ready for use
Suitable for 99% of use cases
Advanced manual initialization: Override default settings at runtime for specific scenarios:
void Awake()
{
// Only for advanced scenarios: feature flags, runtime config, server-driven settings
var settings = new NotificationServicesUnitySettings(
presentationOptions: NotificationPresentationOptions.Alert | NotificationPresentationOptions.Sound,
pushNotificationServiceType: PushNotificationServiceType.Custom);
NotificationServices.Initialize(settings);
}When to use manual initialization:
Feature flag systems controlling notification availability
Server-driven configuration for presentation options
Environment-specific push service types (dev/staging/production)
Runtime permission option configuration based on user preferences
When NOT to use manual initialization:
Standard notification implementation
Default presentation options work for your game
Using settings configured in Essential Kit Settings
No runtime configuration changes needed
Calling Initialize() resets all event listeners. Re-register for OnNotificationReceived and other events after initialization.
Related Guides
Demo scene:
Assets/Plugins/VoxelBusters/EssentialKit/Examples/Scenes/NotificationServicesDemo.unityUse Utilities.OpenApplicationSettings() for permission recovery flows when users deny notifications
Pair with DeepLinkServices to handle notification taps that should navigate to specific in-game content
Combine with CloudServices to sync notification preferences across devices
Ready to test? Head to Testing to validate your notification implementation.
Last updated
Was this helpful?