Tutorial: Push Notifications on desktop and mobile devices

Trigger local and remote notifications in your desktop and mobile applications. Learn how to send push notifications from a remote server to both macOS/iOS and Android devices.

Level: Advanced

Platforms: macOS, iOS, Android

Classes: PushNotifications::Listener, PushNotifications::Notification, PushNotifications::Channel, PushNotifications::Settings

This project requires an Apple Developer account on macOS/iOS and a Google Firebase account on Android. If you need help with this, follow the instructions on the Apple Developer and Google Firebase websites to open up these accounts.
This project also requires a physical device to test push notifications as simulators do not support remote testing. Please make sure you have a device ready for this.
The features supported in Push Notifications will vary depending on platforms and OS versions. For macOS, a minimum of 10.7 is required for remote notifications and 10.8 for local notifications. For Android, a minimum API of 14 (Ice Cream Sandwich) is required for remote notifications and a minimum of 27 for notification channels. The tutorial has a minimum SDK version of 23, but you are welcome to change it to be 14 or above.

Getting started

Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open the first header file in the Projucer.

If using the PIP version of this project, please make sure to copy the Resources folder into the generated Projucer project.

If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.

The demo project

The project provides a comprehensive user interface to send and receive both local and remote notifications. Separate tables are displayed to organise notification parameters and identifiers. If we run the mobile application in the iOS simulator, the window should look something like this:

The demo project app window on iOS
The code presented here is broadly similar to the PushNotificationsDemo from the JUCE Examples.

Initial Setup

In order for this project to function properly, we have to perform some initial setup procedures with appropriate developer consoles for specific deployment platform. Let's first allow appropriate permissions for push notifications in the Projucer. Under macOS and iOS, make sure to enable the Push Notifications capability field. Under Android, make sure to enable the Remote Notifications field.

Project settings window on iOS
Project settings window on Android

We also need to specify custom Xcode resource folders for both macOS and iOS in order for the resources to be bundled in the executable as shown below:

Custom resources on macOS
Custom resources on iOS

Lastly, on Android we have to specify each resource we want to add in the executable individually as raw resources in both "Debug" and "Release" exporter settings as follows:

Raw resources on Android (Debug)
Raw resources on Android (Release)

The full list of resources needed is the following:


The Projucer will automatically add the required entitlements to your deployment targets when saving the project and opening in your favourite IDE.

Apple Developer

If developing for Android, please skip to the next section Google Firebase for instructions.

On macOS and iOS, you will need to sign in with your Apple Developer account within Xcode and choose a development team in order to sign the application. Choose a unique bundle ID for your project. Xcode should automatically provide you with a Signing Certificate and Provisioning Profile as shown in the following screenshot:

General settings window in Xcode

Also make sure that the correct app capabilities have been ticked and approved in the Capabilities settings window. You should see the same information as follows (note there is no Background Modes section on macOS):

Capabilities settings window in Xcode


In order to test remote notifications on iOS, we require a server or an application that will send notifications using the Apple Push Notification Service (APNs). We could make use of Google Firebase to test these (like we do for the Android side of things), but thankfully there is an easier solution using an app called Pusher. We recommend this application for the purpose of this tutorial but you can use any other service of your choice that uses APNs.

The Pusher app to test remote notifications

Generate an SSL Certificate

In order to send push notifications using the Pusher app, we need to generate an SSL certificate. To do this, log in to your Apple Developer account and navigate to your app ID on the Certificates, IDs & Profiles page. You should be able to configure the Push Notifications service by clicking on the Edit button at the bottom of the list. Follow the instructions on the website to create a certificate as shown on the following screenshot:

Generate an SSL Certificate on the Apple Developer portal

Once the certificate is generated and downloaded, double-click on it to add it to your Keychain. You will be able to select it in the Pusher app under the certificate dropdown as it will be listed automatically from your Keychain.

Google Firebase

If developing for macOS/iOS, please jump to the previous section Apple Developer for instructions.

For the purpose of this tutorial, we make use of Google Firebase to test remote notifications on Android. First, create a new project in the Firebase console and add an Android app to the project. Choose a unique package name for your project. If you navigate to the Cloud Messaging tab in the project settings, you can access the server sender ID on the following screen:

Server sender ID in Google Firebase

Insert this sender ID in the following code snippet of the MainContentComponent class:

remoteView.sendRemoteMessageButton.onClick = [this]
data.set ("key1", "value1");
data.set ("key2", "value2");
static int id = 100;
PushNotifications::getInstance()->sendUpstreamMessage ("XXXXXXXXXXXX", // Insert sender ID here
String (id++),
Make sure that the bundle ID in Google Firebase matches the one in your project settings (all in lowercase).

Generate google-services.json file

We also need to download the Firebase configuration file for remote notifications on Android. Navigate to the General tab in the project settings and click on download google-services.json as shown in the following screenshot:

Download json file from Google Firebase

Copy the file in your JUCE project directory and insert the relative path to the google-services.json file under the Android exporter settings in the Remote Notifications Config File field:

Path for google-services file in exporter settings
You can also specify a path to google-services.json separately for each target under the "Debug" and "Release" sections. These will override the settings in the main Android exporter section.

Setup for push notifications should be complete by now and we can finally start implementing these features into the app.

Notification Types

Notifications are useful to keep users informed with relevant content whether the app is running or not. They can simply display a message, show a dialog box or even play a sound. In general, there are two main types of notifications on all relevant platforms:

  • Local: Triggered locally by the system, they do not require internet connection and can be scheduled by the app to fire even when the app is inactive.
  • Remote: Pushed by a remote server, they require internet connection but can be sent anytime based on data received from customer devices.

Let's first look at simple local notifications that we can fire within the app.

Registering as a Listener

The PushNotifications class in JUCE is implemented as a singleton and its global instance can be accessed anytime using the function PushNotifications::getInstance(). The PushNotifications class offers an asynchronous interface where results of actions are propagated using the PushNotifications::Listener interface. Simply register a listener with the PushNotifications instance to receive the callbacks. Consequently in the MainContentComponent class, we inherit from PushNotifications::Listener [1]:

class MainContentComponent : public Component,
private ChangeListener,

In the constructor of the MainContentComponent class, we add this class as a listener to the PushNotifications instance [2]:

PushNotifications::getInstance()->addListener (this); // [2]

We also make sure to unsubscribe in the class destructor like so [3]:

PushNotifications::getInstance()->removeListener (this); // [3]

Requesting Permissions

Before sending any type of notification, we first need to request permission to do so from the user. This is requested only once when the app is first launched and the settings remain saved until app deletion or until the user revokes the rights in the system settings. Therefore it is good practice to request permissions on each application startup and only when permission is not granted will the user be asked to grant it. On macOS/iOS, we call the requestPermissionsWithSettings() function which takes a PushNotifications::Settings object as an argument [4]. On Android, we call the setupChannels() function which takes a PushNotifications::ChannelGroup object and PushNotifications::Channel objects as arguments [5].

paramControls.fireInComboBox.onChange = [this] { delayNotification(); };
PushNotifications::getInstance()->requestPermissionsWithSettings (getNotificationSettings()); // [4]
PushNotifications::ChannelGroup cg { "demoGroup", "demo group" };
PushNotifications::getInstance()->setupChannels ({{ cg }}, getAndroidChannels()); // [5]
Note that setupChannels() is only required from Android Oreo (API 26) onwards and it is ignored on earlier Android versions.

Requesting Permissions on macOS/iOS

Let's first take a look at the macOS/iOS side.

There are three different types of permissions we need to request namely Alerts, Badges and Sounds. To facilitate this initialisation, we have created a getNotificationSettings() function where we insert this code. On the PushNotifications::Settings object, set the corresponding variables to allow these features [6]. If you are deploying on macOS, this is all you need to request and you can return from the function. However, on iOS we have to define Action and Category objects defined as follows:

  • Action: Represents an action that can be presented as a button or a text input to a notification.
  • Category: Represents a set of actions that appear together in a notification.

In the same function, define actions and categories as follows:

static PushNotifications::Settings getNotificationSettings()
PushNotifications::Settings settings; // [6]
settings.allowAlert = true;
settings.allowBadge = true;
settings.allowSound = true;
Action okAction;
okAction.identifier = "okAction";
okAction.title = "OK!";
okAction.style = Action::button;
okAction.triggerInBackground = true;
Action cancelAction;
cancelAction.identifier = "cancelAction";
cancelAction.title = "Cancel";
cancelAction.style = Action::button;
cancelAction.triggerInBackground = true;
cancelAction.destructive = true;
Action textAction;
textAction.identifier = "textAction";
textAction.title = "Enter text";
textAction.style = Action::text;
textAction.triggerInBackground = true;
textAction.destructive = false;
textAction.textInputButtonText = "Ok";
textAction.textInputPlaceholder = "Enter text...";
Category okCategory;
okCategory.identifier = "okCategory";
okCategory.actions = { okAction };
Category okCancelCategory;
okCancelCategory.identifier = "okCancelCategory";
okCancelCategory.actions = { okAction, cancelAction };
Category textCategory;
textCategory.identifier = "textCategory";
textCategory.actions = { textAction };
textCategory.sendDismissAction = true;
settings.categories = { okCategory, okCancelCategory, textCategory }; // [7]
return settings;

Here we have created three different actions and three different categories:

  • OK Action: Specifies the action style as a button with an appropriate "OK" title that triggers in the background.
  • Cancel Action: Specifies the action style as a button with an appropriate "Cancel" title that triggers in the background and appears as destructive.
  • Text Action: Specifies the action style as a text input with appropriate placeholders and button text that triggers in the background.
  • OK Category: Represents only the "OK Action".
  • OK/Cancel Category: Represents the "OK Action" and the "Cancel Action".
  • Text Category: Represents only the "Text Action" and specifies that a dismiss action will be sent.

All these categories are returned in the PushNotifications::Settings object [7].

Requesting Permissions on Android

Now let's take a look at the Android side.

There are different channels of importance that we can define with corresponding settings and these channels can in turn be part of channel groups to separate notifications visually. To facilitate this initialisation, we have created a getAndroidChannels() function where we insert this code. Create three different PushNotifications::Channel objects to specify different parameters [8]:

static Array<PushNotifications::Channel> getAndroidChannels()
using Channel = PushNotifications::Channel;
Channel ch1, ch2, ch3; // [8]
ch1.identifier = "1";
ch1.name = "HighImportance";
ch1.description = "High Priority Channel for important stuff";
ch1.groupId = "demoGroup";
ch1.ledColour = Colours::red;
ch1.bypassDoNotDisturb = true;
ch1.canShowBadge = true;
ch1.enableLights = true;
ch1.enableVibration = true;
ch1.soundToPlay = URL ("demonstrative");
ch1.vibrationPattern = { 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 };
ch2.identifier = "2";
ch2.name = "MediumImportance";
ch2.description = "Medium Priority Channel for standard stuff";
ch2.groupId = "demoGroup";
ch2.ledColour = Colours::yellow;
ch2.canShowBadge = true;
ch2.enableLights = true;
ch2.enableVibration = true;
ch2.soundToPlay = URL ("default_os_sound");
ch2.vibrationPattern = { 1000, 1000 };
ch3.identifier = "3";
ch3.name = "LowImportance";
ch3.lockScreenAppearance = PushNotifications::Notification::dontShow;
ch3.description = "Low Priority Channel for silly stuff";
ch3.groupId = "demoGroup";
return { ch1, ch2, ch3 }; // [9]
  • Channel 1: Represents the highest importance in notifications. We can specify parameters such as colours to be red, "Do Not Disturb" to be bypassed, a custom vibration pattern to be activated and even a custom sound file to be played.
  • Channel 2: Represents a medium importance in notifications. This time we specify colours to be yellow, a different vibration pattern to be activated and a different custom sound file to be played.
  • Channel 3: Represents the lowest importance in notifications. We decide to leave the default parameters on these.

All these channels are returned as an array of PushNotifications::Channel objects belonging to the same PushNotifications::ChannelGroup [9].

The getAndroidChannels() implementation must configure channels correctly by providing at least an identifier, a name and a group ID per channel.
Feel free to experiment and change these settings to get familiar with notification permissions on these platforms.

Setup for notifications is now complete on all platforms.

Local notifications

When the user clicks on the send button in the local notifications tab, the following sendLocalNotification() function is called. This function first creates a PushNotifications::Notification object that we subsequently fill with corresponding parameters from the user interface input [1]. If the notification object is invalid due to incorrect parameters, we optionally show a message for the purpose of this tutorial [2]. Otherwise, if the object is valid we can call the sendLocalNotification() function on the PushNotifications instance [3].

void sendLocalNotification()
fillRequiredParams (n); // [4]
fillOptionalParamsOne (n);
fillOptionalParamsTwo (n);
fillOptionalParamsThree (n);
if (! n.isValid()) // [2]
String requiredFields = "identifier (from iOS 10), title, body and category";
String requiredFields = "channel ID (from Android O), title, body and icon";
String requiredFields = "all required fields";
"Incorrect notifications setup",
"Please make sure that "
+ requiredFields + " are set.");
PushNotifications::getInstance()->sendLocalNotification (n); // [3]

For now, we will focus on the required parameters that we fill in the fillRequiredParams() function where we pass the previously defined PushNotifications::Notification object as an argument [4].

If interested by the optional parameters of push notifications, please refer to the last section of this tutorial Customising notifications where we will cover this in depth.
void fillRequiredParams (PushNotifications::Notification& n)
n.identifier = paramControls.identifierEditor.getText(); // [5.1]
n.title = paramControls.titleEditor.getText(); // [5.2]
n.body = paramControls.bodyEditor.getText(); // [5.3]
n.category = paramControls.categoryComboBox.getText(); // [6.1]
String prefix = "images/";
String extension = ".png";
String prefix;
String extension;
if (paramControls.iconComboBox.getSelectedItemIndex() == 0) // [7]
n.icon = prefix + "ic_stat_name" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 1)
n.icon = prefix + "ic_stat_name2" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 2)
n.icon = prefix + "ic_stat_name3" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 3)
n.icon = prefix + "ic_stat_name4" + extension;
else if (paramControls.iconComboBox.getSelectedItemIndex() == 4)
n.icon = prefix + "ic_stat_name5" + extension;
// Note: this is not strictly speaking required param, just doing it here because it is the fastest way!
n.publicVersion->identifier = "blahblahblah";
n.publicVersion->title = "Public title!";
n.publicVersion->body = "Public body!";
n.publicVersion->icon = n.icon;
n.channelId = String (paramControls.channelIdComboBox.getSelectedItemIndex() + 1); // [6.2]

On all platforms, a notification is defined with three main parameters that we set from user input of the required parameter tab:

  • Identifier [5.1]: A unique ID to identify notifications internally. This is generally not shown to the user.
  • Title [5.2]: The title of the notification shown at the top of the bubble.
  • Body [5.3]: The content of the notification that can be expanded to show its totality if trimmed.

On macOS/iOS, we additionally provide the notification category to choose which actions to display [6.1]. On Android, we specify the channel number to specify the importance of the notification [6.2].

On macOS and Android, we need to provide an icon to show within the notification and we make sure that the path is platform-specific [7]. Finally on Android, we can optionally provide a different version of a notification that gets displayed on the device lock screen if we wish [8].

Local notifications are fairly trivial to implement so let's dive into remote notifications now.

Remote notifications

This section will only work on a physical device. Do not attempt on a simulator as it will not function properly.

In order to receive remote notifications on macOS/iOS, we first need to get a device token that we will insert in the Pusher application to send test notifications. This token may change at anytime and it is therefore recommended to fetch a new token every time the application is launched. This is performed in the button lambda function by calling the getDeviceToken() function on the PushNotifications instance [1]:

remoteView.getDeviceTokenButton.onClick = [this]
String token = PushNotifications::getInstance()->getDeviceToken();
DBG ("token = " + token);
if (token.isEmpty())

As expected whenever the token is refreshed, a callback function called deviceTokenRefreshed() is triggered to notify us of this change:

void deviceTokenRefreshed (const String& token) override
"Device token refreshed",

Remote notifications on macOS/iOS

Let's jump to Pusher and see how we can send push notifications to our app on macOS/iOS. Copy the new device token from the debugger and launch the Pusher app. Paste the token in the appropriate Device push token field and make sure that the correct certificate is selected in the dropdown menu.

In the text field below, we can create our push notifications as a payload dictionary using the JSON format. A simple notification will look something like this:

"aps" :
"alert" : "Test Push Notification",
"badge" : 1,
"sound" : "default"
"juce" : "tutorial",
"foo" : "bar"

The first element called "aps" will contain the essential information for our notification. In the above example, it describes the alert, the badge count and the sound to play when triggered. We can also send application specific data as shown in the example with the key/value pairs following the "aps" element.

In this second payload example, we customise the alert to contain more information such as a body and an action button in addition to the title field. We also decide to play a custom sound file when the notification is triggered:

"aps" :
"alert" :
"title" : "Test Push Notification",
"body" : "Hello World!",
"action-loc-key" : "OK"
"badge" : 2,
"sound" : "demonstrative.caf"

In the third example, we make use of the notification categories defined in the PushNotifications::Settings to easily define actions. We also instruct the OS to deliver the notification silently in the background by setting the content-available property:

"aps" :
"category" : "okCategory",
"alert" : "Test Push Notification",
"badge" : 3,
"content-available" : 1
"foobar" : [ "foo", "bar" ]
Experiment with different payload parameters and see how they are received within the app. Can you display an image in the notification body?

Remote notifications on Android

Let's switch to the Android side now and make use of the Google Firebase console. In the console, navigate to the Grow > Notifications tab and click on Compose message. In the following window, you will be able to fill in the information for the message and select which target we want to send the notification to.

On Android, we can also send messages upstream from the app to the server to communicate important user data. This is performed by defining a dictionary of key/value pairs as a StringPairArray object [1] and calling the sendUpstreamMessage() function on the PushNotifications instance [2]:

remoteView.sendRemoteMessageButton.onClick = [this]
StringPairArray data; // [1]
data.set ("key1", "value1");
data.set ("key2", "value2");
static int id = 100;
PushNotifications::getInstance()->sendUpstreamMessage ("XXXXXXXXXXXX", // Insert sender ID here
String (id++),
data); // [2]

Unfortunately, we need to explicitly pass the previously set server sender ID as an argument every time to do this.

When an upstream message is sent to the server we can expect a callback with the following upstreamMessageSent() function if the request was successful:

void upstreamMessageSent (const String& messageId) override
"Upstream message sent",
"Message id: " + messageId);

However if the request is unsuccessful, we receive the callback with the upstreamMessageSendingError() function instead:

void upstreamMessageSendingError (const String& messageId, const String& error) override
"Upstream message sending error",
"Message id: " + messageId
+ "\nerror: " + error);

If the Google Firebase receives too many messages, it may start deleting them from the queue of pending messages. When this happens, we are notified via the remoteNotificationsDeleted() function:

void remoteNotificationsDeleted() override
"Remote notifications deleted",
"Some of the pending messages were removed!");

On Android we can also subscribe and unsubscribe to specific topics by calling the subscribeToTopic() and unsubscribeFromTopic() functions respectively on the PushNotifications instance. In this example we choose to subscribe to and unsubscribe from the "sports" topic:

remoteView.subscribeToSportsButton .onClick = [this]
{ PushNotifications::getInstance()->subscribeToTopic ("sports"); };
remoteView.unsubscribeFromSportsButton.onClick = [this]
{ PushNotifications::getInstance()->unsubscribeFromTopic ("sports"); };

Handling received notifications

There is a number of different callbacks that can be invoked when a user acts on a notification. The exact behaviour may vary between platforms and OS versions so please refer to the PushNotifications class documentation for a detailed explanation.

The handleNotification() callback function will be invoked whenever a user presses on a notification. Normally you would use this function to process information from the notification based on the ID but for the purpose of this tutorial we simply show a message box with the three main parameters as follows:

void handleNotification (bool isLocalNotification, const PushNotifications::Notification& n) override
ignoreUnused (isLocalNotification);
"Received notification",
"ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body);

The handleNotificationAction() callback function will be invoked whenever a user performs an action from a notification (such as pressing a button or entering a text input). This callback contains additional information pertaining to the type of action and an optional response in the form of text input for example. In this scenario, we also need to manually remove the notification from the history by providing the notification identifer to the removeDeliveredNotification() function as an argument and by calling it on the PushNotifications instance [9]:

void handleNotificationAction (bool isLocalNotification,
const String& actionIdentifier,
const String& optionalResponse) override
ignoreUnused (isLocalNotification);
"Received notification action",
"ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body
+ ", action: " + actionIdentifier
+ ", optionalResponse: " + optionalResponse);
PushNotifications::getInstance()->removeDeliveredNotification (n.identifier); // [9]

As the name suggests, the following callback is triggered when the user dismisses a local notification before responding to it and we display the same message box as before:

void localNotificationDismissedByUser (const PushNotifications::Notification& n) override
"Notification dismissed by a user",
"ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body);

If the user ignores a notification or does not act upon it, the notification stays in the list of delivered ones in the notification area of the device. To retrieve the list of delivered notifications, we can call the getDeliveredNotifications() function on the PushNotifications instance. A callback function named deliveredNotificationsListReceived() will subsequently be called and we can handle it by displaying the list in a message box as follows:

void deliveredNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
String text = "Received notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
NativeMessageBox::showMessageBoxAsync (AlertWindow::InfoIcon, "Received notification list", text);

On macOS/iOS, the application can schedule local notifications to be triggered at a given moment in the future. This convenient callback function named pendingLocalNotificationsListReceived() will receive an array of pending notifications when the getPendingLocalNotifications() function is called on the PushNotifications instance:

void pendingLocalNotificationsListReceived (const Array<PushNotifications::Notification>& notifs) override
String text = "Pending notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
NativeMessageBox::showMessageBoxAsync (AlertWindow::InfoIcon, "Pending notification list", text);
Experiment with different parameters and display these in the message boxes. Can you display the notification icon in the callback message box?

The core of this tutorial has been demonstrated and all callback functions from the PushNotifications::Listener class were overriden but if you wish to learn more about optional notification parameters, please feel free to read on!

Customising notifications

Notifications have numerous optional parameters that we can set in addition to the ones we have discovered so far in this tutorial. This optional section will cover these parameters in detail.

This first function called in the sendLocalNotification() function fills some optional parameters supported by all platforms:

void fillOptionalParamsOne (PushNotifications::Notification& n)
n.subtitle = paramControls.subtitleEditor.getText(); // [1.1]
n.badgeNumber = paramControls.badgeNumberComboBox.getSelectedItemIndex(); // [1.2]
if (paramControls.soundToPlayComboBox.getSelectedItemIndex() > 0)
n.soundToPlay = URL (paramControls.soundToPlayComboBox.getItemText (paramControls.soundToPlayComboBox.getSelectedItemIndex())); // [1.3]
n.properties = JSON::parse (paramControls.propertiesEditor.getText()); // [1.4]
n.triggerIntervalSec = double (paramControls.fireInComboBox.getSelectedItemIndex() * 10);
n.repeat = paramControls.repeatButton.getToggleState();
n.badgeIconType = (PushNotifications::Notification::BadgeIconType) paramControls.badgeIconComboBox.getSelectedItemIndex(); // [2.1]
n.tickerText = paramControls.tickerTextEditor.getText(); // [2.2]
n.shouldAutoCancel = paramControls.autoCancelButton.getToggleState(); // [2.3]
n.alertOnlyOnce = paramControls.alertOnlyOnceButton.getToggleState(); // [2.4]
if (paramControls.actionsComboBox.getSelectedItemIndex() == 1)
a .title = a .identifier = "Ok";
a2.title = a2.identifier = "Cancel";
n.actions.add (a);
n.actions.add (a2);
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2)
a .title = a .identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
a.textInputPlaceholder = "placeholder text ...";
n.actions.add (a);
n.actions.add (a2);
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 3)
a .title = a .identifier = "Ok";
a2.title = a2.identifier = "Cancel";
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
n.actions.add (a);
n.actions.add (a2);
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4)
a .title = a .identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a .icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
a.textInputPlaceholder = "placeholder text ...";
a.allowedResponses.add ("Response 1");
a.allowedResponses.add ("Response 2");
a.allowedResponses.add ("Response 3");
n.actions.add (a);
n.actions.add (a2);
  • Subtitle [1.1]: Additional text that can be displayed in the notification.
  • Badge Number [1.2]: A number representing the count displayed in the badge icon before it is summed with other delivered notification badge numbers.
  • Sound [1.3]: A custom sound to play when the notification is triggered. You can leave this field empty if you want the notification to be silent or use "default_os_sound" to trigger the default OS sound.
  • Properties [1.4]: Additional properties that may be passed as a dictionary.

On macOS and iOS, you can also delay the trigger of a notification by a specified amount in seconds and decide to repeat the notification in question. On Android, you can provide a large image to display in the content of the notification and specify these additional parameters:

  • Badge Icon Type [2.1]: The size of the badge icon or whether we want to hide it.
  • Ticker Text [2.2]: Additional text used for accessibility.
  • Auto Cancel [2.3]: Whether the notification should be canceled when clicked on it.
  • Alert Once [2.4]: Whether the notification should only sound and vibrate when not already showing.

If you wish to have action buttons displayed in the notifications like on iOS, in Android and macOS we have to define them manually. This setup is essentially similar to the iOS one using the PushNotifications::Settings object so we will not go into detail.

The next sets of parameters are Android only.

void fillOptionalParamsTwo (PushNotifications::Notification& n)
using Notification = PushNotifications::Notification;
Notification::Progress progress;
progress.max = paramControls.progressMaxComboBox.getSelectedItemIndex() * 10;
progress.current = paramControls.progressCurrentComboBox.getSelectedItemIndex() * 10;
progress.indeterminate = paramControls.progressIndeterminateButton.getToggleState();
n.progress = progress; // [3.1]
n.person = paramControls.personEditor.getText(); // [3.2]
n.type = Notification::Type (paramControls.categoryComboBox.getSelectedItemIndex()); // [3.3]
n.priority = Notification::Priority (paramControls.priorityComboBox.getSelectedItemIndex() - 2); // [3.4]
n.lockScreenAppearance = Notification::LockScreenAppearance (paramControls.lockScreenVisibilityComboBox.getSelectedItemIndex() - 1); // [3.5]
n.groupId = paramControls.groupIdEditor.getText(); // [3.6]
n.groupSortKey = paramControls.sortKeyEditor.getText();
n.groupSummary = paramControls.groupSummaryButton.getToggleState();
n.groupAlertBehaviour = Notification::GroupAlertBehaviour (paramControls.groupAlertBehaviourComboBox.getSelectedItemIndex());
  • Progress [3.1]: Displays specific types of notifications that show progress.
  • Person [3.2]: Relates the notification to a specific person. This can be useful for instance in messaging apps.
  • Type [3.3]: Specifies the category of notification for the OS to better handle the appearance.
  • Priority [3.4]: Specifies the priority of notification to the OS to better handle the appearance.
  • Lock Screen Appearance [3.5]: Whether to show or hide the notification on the lock screen.
  • Group [3.6]: Specifies parameters relating to groups of notifications such as sorting order, group summary and group alert behaviour.
void fillOptionalParamsThree (PushNotifications::Notification& n)
n.accentColour = paramControls.accentColourButton.findColour (TextButton::buttonColourId, false); // [4.1]
n.ledColour = paramControls.ledColourButton .findColour (TextButton::buttonColourId, false); // [4.2]
using Notification = PushNotifications::Notification;
Notification::LedBlinkPattern ledBlinkPattern;
ledBlinkPattern.msToBeOn = paramControls.ledMsToBeOnComboBox .getSelectedItemIndex() * 200;
ledBlinkPattern.msToBeOff = paramControls.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
n.ledBlinkPattern = ledBlinkPattern; // [4.3]
Array<int> vibrationPattern;
if (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() > 0 &&
paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() > 0)
vibrationPattern.add (paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
vibrationPattern.add (paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
vibrationPattern.add (2 * paramControls.vibratorMsToBeOffComboBox.getSelectedItemIndex() * 500);
vibrationPattern.add (2 * paramControls.vibratorMsToBeOnComboBox .getSelectedItemIndex() * 500);
n.vibrationPattern = vibrationPattern; // [4.4]
n.localOnly = paramControls.localOnlyButton.getToggleState(); // [4.5]
n.ongoing = paramControls.ongoingButton.getToggleState(); // [4.6]
n.timestampVisibility = Notification::TimestampVisibility (paramControls.timestampVisibilityComboBox.getSelectedItemIndex()); // [4.7]
if (paramControls.timeoutAfterComboBox.getSelectedItemIndex() > 0)
auto index = paramControls.timeoutAfterComboBox.getSelectedItemIndex();
n.timeoutAfterMs = index * 1000 + 4000; // [4.8]
  • Accent Colour [4.1]: Changes the accent colour of the notification.
  • LED Colour [4.2]: Changes the colour of the physical LED on the back of the device.
  • LED Blink Pattern [4.3]: Allows customisation of the blinking pattern of the physical LED.
  • Vibration Pattern [4.4]: Allows customisation of the vibration pattern of the physical device.
  • Local [4.5]: Whether the notification should be broadcast to other connected user devices.
  • Ongoing [4.6]: Whether the user is allowed to dismiss the notification or the system can only dismiss it manually.
  • Timestamp [4.7]: Whether a timestamp or chronometer is displayed in the notification.
  • Timeout [4.8]: Specifies the time after which the notification is automatically canceled if not already.
The source code for this modified version of the code can be found in the PushNotificationsTutorial_02.h file of the demo project.


In this tutorial, we have learnt how to handle Push Notifications on mobile and desktop. In particular, we have:

  • Covered preliminary setup for different deployment platforms.
  • Requested user permissions from the system to allow notifications.
  • Displayed simple messages as local notifications from within the app.
  • Handled push notifications that were sent from a remote server.
  • Customised notifications by showing pictures and playing sounds.

See also

String tickerText
Optional: ticker text used for accessibility services.
Definition: juce_PushNotifications.h:181
Type type
Definition: juce_PushNotifications.h:234
Available value types.
Definition: juce_LittleFootRunner.h:128
void set(const String &key, const String &value)
Adds or amends a key/value pair.
String subtitle
Optional: additional text, that may be displayed e.g.
Definition: juce_PushNotifications.h:122
bool isEmpty() const noexcept
Returns true if the string contains no characters.
Definition: juce_String.h:299
The notification is not allowed on the lock screen.
Definition: juce_PushNotifications.h:240
Show this action as a button.
Definition: juce_PushNotifications.h:75
bool alertOnlyOnce
Optional: Set this flag if you would only like the sound, vibrate and ticker to be played if the noti...
Definition: juce_PushNotifications.h:287
BadgeIconType badgeIconType
Definition: juce_PushNotifications.h:309
String title
Required: the title of the notification, usually displayed in the first row.
Definition: juce_PushNotifications.h:120
bool localOnly
Optional: whether or not the notification should bridge to other devices.
Definition: juce_PushNotifications.h:279
Definition: juce_PushNotifications.h:463
static Result parse(const String &text, var &parsedResult)
Parses a string of JSON-formatted text, and returns a result code containing any parse errors.
An icon that indicates that the dialog box is just giving the user some information,...
Definition: juce_AlertWindow.h:60
String icon
Optional: name of an icon file (without an extension) to be used for this action.
Definition: juce_PushNotifications.h:101
The JUCE String class!
Definition: juce_String.h:41
String groupId
Optional: allows the OS to visually group, collapse, and expand a set of notifications,...
Definition: juce_PushNotifications.h:127
String channelId
Required for Android API level 26 or above: specifies notification channel id.
Definition: juce_PushNotifications.h:176
String identifier
Required: unique identifier.
Definition: juce_PushNotifications.h:90
Android API level 26 or higher only: Represents notification channel through which notifications will...
Definition: juce_PushNotifications.h:450
String groupSortKey
Optional: Used to order notifications within the same group.
Definition: juce_PushNotifications.h:252
Definition: juce_TargetPlatform.h:68
Array< Action > actions
Optional: actions associated with the notification.
Definition: juce_PushNotifications.h:183
String title
Definition: juce_PushNotifications.h:82
Only some information is allowed on the lock screen.
Definition: juce_PushNotifications.h:241
Gets informed about changes to a component's hierarchy or position.
Definition: juce_ComponentListener.h:46
String identifier
Required: unique id that can be used to later dismiss the notification (on iOS available from version...
Definition: juce_PushNotifications.h:117
Style style
Definition: juce_PushNotifications.h:81
Definition: juce_PushNotifications.h:459
double triggerIntervalSec
Optional: specifies number of seconds before the notification should trigger.
Definition: juce_PushNotifications.h:161
The entire notification is allowed on the lock screen.
Definition: juce_PushNotifications.h:242
const Colour red
Definition: juce_Colours.h:161
std::unique_ptr< Notification > publicVersion
Optional: if you set lockScreenAppearance to showPartially, then you can provide "public version" of ...
Definition: juce_PushNotifications.h:247
var properties
Optional: collection of additional properties that may be passed as a dictionary.
Definition: juce_PushNotifications.h:151
Represents a notification that can be sent or received.
Definition: juce_PushNotifications.h:58
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:58
Describes settings we want to use for current device.
Definition: juce_PushNotifications.h:391
TimestampVisibility timestampVisibility
Definition: juce_PushNotifications.h:299
void ignoreUnused(Types &&...) noexcept
Handy function for avoiding unused variables warning.
Definition: juce_MathsFunctions.h:292
String person
Optional: additional metadata used as a hint to OS that a notification is related to a specific perso...
Definition: juce_PushNotifications.h:230
Android API level 26 or higher only: represents a channel group.
Definition: juce_PushNotifications.h:485
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:421
Progress progress
Optional: set to default (0, 0, false), to disable progress display.
Definition: juce_PushNotifications.h:197
void add(String stringToAdd)
Appends a string at the end of the array.
Array< int > vibrationPattern
Optional: sets the vibration pattern in milliseconds.
Definition: juce_PushNotifications.h:270
The colour used to fill the button shape (when the button is toggled 'off').
Definition: juce_TextButton.h:77
String identifier
Required: Unique channel identifier.
Definition: juce_PushNotifications.h:452
bool isValid() const noexcept
Checks whether a given notification is correctly configured for a given OS.
Receives change event callbacks that are sent out by a ChangeBroadcaster.
Definition: juce_ChangeListener.h:47
Notification::Action Action
Definition: juce_PushNotifications.h:393
Colour ledColour
Optional: Sets the led colour.
Definition: juce_PushNotifications.h:258
static void JUCE_CALLTYPE showMessageBoxAsync(AlertWindow::AlertIconType iconType, const String &title, const String &message, Component *associatedComponent=nullptr, ModalComponentManager::Callback *callback=nullptr)
Shows a dialog box that just has a message and a single 'ok' button to close it.
URL soundToPlay
Optional: empty when the notification should be silent.
Definition: juce_PushNotifications.h:132
bool shouldAutoCancel
Optional: If true, the notification will be automatically cancelled when a user clicks it in the pane...
Definition: juce_PushNotifications.h:277
juce::String identifier
unique identifier
Definition: juce_PushNotifications.h:402
const Colour yellow
Definition: juce_Colours.h:186
Represents a URL and has a bunch of useful functions to manipulate it.
Definition: juce_URL.h:40
bool allowAlert
whether the app should present an alert upon notification
Definition: juce_PushNotifications.h:408
Represents an action on a notification that can be presented as a button or a text input.
Definition: juce_PushNotifications.h:70
int badgeNumber
Optional: on platforms that support it, can set a number this notification represents.
Definition: juce_PushNotifications.h:131
Definition: juce_PushNotifications.h:461
String category
Required: determines set of actions that will appear (as per setup done in requestPermissionsWithSett...
Definition: juce_PushNotifications.h:159
StringArray allowedResponses
Optional: a list of possible answers if the answer set is limited.
Definition: juce_PushNotifications.h:108
String icon
Required: name of an icon file (without an extension) to be used for this notification.
Definition: juce_PushNotifications.h:171
bool allowBadge
whether the app may badge its icon upon notification
Definition: juce_PushNotifications.h:409
The base class for all JUCE user-interface objects.
Definition: juce_Component.h:39
String body
Required: the content of the notification, usually displayed in the second row.
Definition: juce_PushNotifications.h:121
LockScreenAppearance lockScreenAppearance
Definition: juce_PushNotifications.h:245
int timeoutAfterMs
specifies a duration in milliseconds, after which the notification should be cancelled,...
Definition: juce_PushNotifications.h:323
String textInputPlaceholder
Optional: placeholder text for text input notification.
Definition: juce_PushNotifications.h:83
bool groupSummary
Optional: if true, then this notification will be a group summary of the group set with groupId.
Definition: juce_PushNotifications.h:253
Controls badge icon type to use if a notification is shown as a badge.
Definition: juce_PushNotifications.h:302
bool ongoing
Optional: If true, then it cannot be dismissed by the user and it must be dismissed manually.
Definition: juce_PushNotifications.h:282
#define DBG(textToWrite)
Writes a string to the standard error stream.
Definition: juce_PlatformDefs.h:124
bool repeat
Optional: allows the notification to continuously retrigger after triggerIntervalSec seconds.
Definition: juce_PushNotifications.h:162
Show this action as a text input field (on Android API 20 or higher is required).
Definition: juce_PushNotifications.h:76
Priority priority
Definition: juce_PushNotifications.h:235
A container for holding a set of strings which are keyed by another string.
Definition: juce_StringPairArray.h:37
Describes a category of a notification.
Definition: juce_PushNotifications.h:400
Colour accentColour
Optional: sets accent colour.
Definition: juce_PushNotifications.h:256
Array< Category > categories
list of categories the app wants to support
Definition: juce_PushNotifications.h:410
LedBlinkPattern ledBlinkPattern
Definition: juce_PushNotifications.h:268
GroupAlertBehaviour groupAlertBehaviour
Definition: juce_PushNotifications.h:321
Register a listener (ideally on application startup) to receive information about notifications recei...
Definition: juce_PushNotifications.h:597
bool allowSound
whether the app should play a sound upon notification
Definition: juce_PushNotifications.h:407