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. [!WARNING] 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. [!NOTE] 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.
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 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 code presented here is broadly similar to the PushNotificationsDemo from the JUCE Examples.
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.


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:


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:


The full list of resources needed is the following:
../Resources/sounds/demonstrative.mp3
../Resources/sounds/isntit.mp3
../Resources/sounds/jinglebellssms.mp3
../Resources/sounds/served.mp3
../Resources/sounds/solemn.mp3
../Resources/images/ic_stat_name.png
../Resources/images/ic_stat_name2.png
../Resources/images/ic_stat_name3.png
../Resources/images/ic_stat_name4.png
../Resources/images/ic_stat_name5.png
The Projucer will automatically add the required entitlements to your deployment targets when saving the project and opening in your favourite IDE.
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:

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):

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.

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:

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.
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:

Insert this sender ID in the following code snippet of the MainContentComponent constructor:
#if JUCE_ANDROID
remoteView.sendRemoteMessageButton.onClick = [this] {
juce::StringPairArray data;
data.set ("key1", "value1");
data.set ("key2", "value2");
static int id = 100;
juce::PushNotifications::getInstance()->sendUpstreamMessage ("XXXXXXXXXXXX", // Insert sender ID here
"com.juce.pushnotificationstutorial",
juce::String (id++),
"standardType",
3600,
data);
};
Make sure that the bundle ID in Google Firebase matches the one in your project settings (all in lowercase).
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:

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:

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.
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:
Let's first look at simple local notifications that we can fire within the app.
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 juce::Component,
private juce::ChangeListener,
private juce::ComponentListener,
private juce::PushNotifications::Listener // [1]
{
In the constructor of the MainContentComponent class, we add this class as a listener to the PushNotifications instance [2]:
juce::PushNotifications::getInstance()->addListener (this); // [2]
We also make sure to unsubscribe in the class destructor like so [3]:
~MainContentComponent() override
{
juce::PushNotifications::getInstance()->removeListener (this); // [3]
}
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].
#if JUCE_IOS || JUCE_MAC
paramControls.fireInComboBox.onChange = [this] { delayNotification(); };
juce::PushNotifications::getInstance()->requestPermissionsWithSettings (getNotificationSettings()); // [4]
#elif JUCE_ANDROID
juce::PushNotifications::ChannelGroup cg { "demoGroup", "demo group" };
juce::PushNotifications::getInstance()->setupChannels ({ { cg } }, getAndroidChannels()); // [5]
#endif
}
Note that setupChannels() is only required from Android Oreo (API 26) onwards and it is ignored on earlier Android versions.
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:
In the same function, define actions and categories as follows:
static juce::PushNotifications::Settings getNotificationSettings()
{
juce::PushNotifications::Settings settings; // [6]
settings.allowAlert = true;
settings.allowBadge = true;
settings.allowSound = true;
#if JUCE_IOS
using Action = juce::PushNotifications::Settings::Action;
using Category = juce::PushNotifications::Settings::Category;
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]
#endif
return settings;
}
Here we have created three different actions and three different categories:
All these categories are returned in the PushNotifications::Settings object [7].
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 juce::Array<juce::PushNotifications::Channel> getAndroidChannels()
{
using Channel = juce::PushNotifications::Channel;
Channel ch1, ch2, ch3;
ch1.identifier = "1";
ch1.name = "HighImportance";
ch1.importance = juce::PushNotifications::Channel::max;
ch1.lockScreenAppearance = juce::PushNotifications::Notification::showCompletely;
ch1.description = "High Priority Channel for important stuff";
ch1.groupId = "demoGroup";
ch1.ledColour = juce::Colours::red;
ch1.bypassDoNotDisturb = true;
ch1.canShowBadge = true;
ch1.enableLights = true;
ch1.enableVibration = true;
ch1.soundToPlay = juce::URL ("demonstrative");
ch1.vibrationPattern = { 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 };
ch2.identifier = "2";
ch2.name = "MediumImportance";
ch2.importance = juce::PushNotifications::Channel::normal;
ch2.lockScreenAppearance = juce::PushNotifications::Notification::showPartially;
ch2.description = "Medium Priority Channel for standard stuff";
ch2.groupId = "demoGroup";
ch2.ledColour = juce::Colours::yellow;
ch2.canShowBadge = true;
ch2.enableLights = true;
ch2.enableVibration = true;
ch2.soundToPlay = juce::URL ("default_os_sound");
ch2.vibrationPattern = { 1000, 1000 };
ch3.identifier = "3";
ch3.name = "LowImportance";
ch3.importance = juce::PushNotifications::Channel::min;
ch3.lockScreenAppearance = juce::PushNotifications::Notification::dontShow;
ch3.description = "Low Priority Channel for silly stuff";
ch3.groupId = "demoGroup";
return { ch1, ch2, ch3 };
}
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.
Exercise: 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.
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()
{
juce::PushNotifications::Notification n; // [1]
fillRequiredParams (n); // [4]
fillOptionalParamsOne (n);
#if JUCE_ANDROID
fillOptionalParamsTwo (n);
fillOptionalParamsThree (n);
#endif
if (!n.isValid()) // [2]
{
#if JUCE_IOS
juce::String requiredFields = "identifier (from iOS 10), title, body and category";
#elif JUCE_ANDROID
juce::String requiredFields = "channel ID (from Android O), title, body and icon";
#else
juce::String requiredFields = "all required fields";
#endif
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"Incorrect notifications setup",
"Please make sure that "
+ requiredFields + " are set.");
return;
}
juce::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 (juce::PushNotifications::Notification& n)
{
n.identifier = paramControls.identifierEditor.getText(); // [5.1]
n.title = paramControls.titleEditor.getText(); // [5.2]
n.body = paramControls.bodyEditor.getText(); // [5.3]
#if JUCE_IOS
n.category = paramControls.categoryComboBox.getText(); // [6.1]
#elif JUCE_ANDROID || JUCE_MAC
#if JUCE_MAC
juce::String prefix = "images/";
juce::String extension = ".png";
#else
juce::String prefix;
juce::String extension;
#endif
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;
#endif
#if JUCE_ANDROID
// Note: this is not strictly speaking required param, just doing it here because it is the fastest way!
n.publicVersion = new juce::PushNotifications::Notification(); // [8]
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]
#endif
}
On all platforms, a notification is defined with three main parameters that we set from user input of the required parameter tab:
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.
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 = [] {
juce::String token = juce::PushNotifications::getInstance()->getDeviceToken();
DBG ("token = " + token);
if (token.isEmpty())
showRemoteInstructions();
else
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon, "Device token", token);
};
As expected whenever the token is refreshed, a callback function called deviceTokenRefreshed() is triggered to notify us of this change:
void deviceTokenRefreshed (const juce::String& token) override
{
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"Device token refreshed",
token);
}
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"]
}
Exercise: Experiment with different payload parameters and see how they are received within the app. Can you display an image in the notification body?
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] {
juce::StringPairArray data; // [1]
data.set ("key1", "value1");
data.set ("key2", "value2");
static int id = 100;
juce::PushNotifications::getInstance()->sendUpstreamMessage ("XXXXXXXXXXXX", // Insert sender ID here
"com.juce.pushnotificationstutorial",
String (id++),
"standardType",
3600,
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 juce::String& messageId) override
{
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"Upstream message sent",
"Message id: " + messageId);
}
However if the request is unsuccessful, we receive the callback with the upstreamMessageSendingError() function instead:
void upstreamMessageSendingError (const juce::String& messageId, const juce::String& error) override
{
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"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
{
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"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] { juce::PushNotifications::getInstance()->subscribeToTopic ("sports"); };
remoteView.unsubscribeFromSportsButton.onClick = [this] { juce::PushNotifications::getInstance()->unsubscribeFromTopic ("sports"); };
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 juce::PushNotifications::Notification& n) override
{
juce::ignoreUnused (isLocalNotification);
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"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 juce::PushNotifications::Notification& n,
const juce::String& actionIdentifier,
const juce::String& optionalResponse) override
{
juce::ignoreUnused (isLocalNotification);
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"Received notification action",
"ID: " + n.identifier
+ ", title: " + n.title
+ ", body: " + n.body
+ ", action: " + actionIdentifier
+ ", optionalResponse: " + optionalResponse);
juce::PushNotifications::getInstance()->removeDeliveredNotification (n.identifier);
}
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 juce::PushNotifications::Notification& n) override
{
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon,
"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 juce::Array<juce::PushNotifications::Notification>& notifs) override
{
juce::String text = "Received notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
juce::NativeMessageBox::showMessageBoxAsync (juce::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 juce::Array<juce::PushNotifications::Notification>& notifs) override
{
juce::String text = "Pending notifications: ";
for (auto& n : notifs)
text << "(" << n.identifier << ", " << n.title << ", " << n.body << "), ";
juce::NativeMessageBox::showMessageBoxAsync (juce::AlertWindow::InfoIcon, "Pending notification list", text);
}
Exercise: 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!
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 (juce::PushNotifications::Notification& n)
{
n.subtitle = paramControls.subtitleEditor.getText(); // [1.1]
n.badgeNumber = paramControls.badgeNumberComboBox.getSelectedItemIndex(); // [1.2]
if (paramControls.soundToPlayComboBox.getSelectedItemIndex() > 0)
n.soundToPlay = juce::URL (paramControls.soundToPlayComboBox.getItemText (paramControls.soundToPlayComboBox.getSelectedItemIndex())); // [1.3]
n.properties = juce::JSON::parse (paramControls.propertiesEditor.getText()); // [1.4]
#if JUCE_IOS || JUCE_MAC
n.triggerIntervalSec = double (paramControls.fireInComboBox.getSelectedItemIndex() * 10);
n.repeat = paramControls.repeatButton.getToggleState();
#elif JUCE_ANDROID
n.badgeIconType = (juce::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]
#endif
#if JUCE_ANDROID || JUCE_MAC
if (paramControls.actionsComboBox.getSelectedItemIndex() == 1)
{
juce::PushNotifications::Notification::Action a, a2;
a.style = juce::PushNotifications::Notification::Action::button;
a2.style = juce::PushNotifications::Notification::Action::button;
a.title = a.identifier = "Ok";
a2.title = a2.identifier = "Cancel";
n.actions.add (a);
n.actions.add (a2);
}
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 2)
{
juce::PushNotifications::Notification::Action a, a2;
a.title = a.identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a.style = juce::PushNotifications::Notification::Action::text;
a2.style = juce::PushNotifications::Notification::Action::button;
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)
{
juce::PushNotifications::Notification::Action a, a2;
a.title = a.identifier = "Ok";
a2.title = a2.identifier = "Cancel";
a.style = juce::PushNotifications::Notification::Action::button;
a2.style = juce::PushNotifications::Notification::Action::button;
a.icon = "ic_stat_name4";
a2.icon = "ic_stat_name5";
n.actions.add (a);
n.actions.add (a2);
}
else if (paramControls.actionsComboBox.getSelectedItemIndex() == 4)
{
juce::PushNotifications::Notification::Action a, a2;
a.title = a.identifier = "Input Text Here";
a2.title = a2.identifier = "No";
a.style = juce::PushNotifications::Notification::Action::text;
a2.style = juce::PushNotifications::Notification::Action::button;
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);
}
#endif
}
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:
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 (juce::PushNotifications::Notification& n)
{
using Notification = juce::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());
}
void fillOptionalParamsThree (juce::PushNotifications::Notification& n)
{
n.accentColour = paramControls.accentColourButton.findColour (juce::TextButton::buttonColourId, false); // [4.1]
n.ledColour = paramControls.ledColourButton.findColour (juce::TextButton::buttonColourId, false); // [4.2]
using Notification = juce::PushNotifications::Notification;
Notification::LedBlinkPattern ledBlinkPattern;
ledBlinkPattern.msToBeOn = paramControls.ledMsToBeOnComboBox.getSelectedItemIndex() * 200;
ledBlinkPattern.msToBeOff = paramControls.ledMsToBeOffComboBox.getSelectedItemIndex() * 200;
n.ledBlinkPattern = ledBlinkPattern; // [4.3]
juce::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]
}
}
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: