React Native: Working with Notification Badges

How to invoke and dismiss iOS notification badges in React Native and server-side

Image for post
Image for post

Effectively get user attention with notification badges

App badges are tied into Apple’s push notification service. They can be displayed beside the app icon on the home screen and within the app itself. Not only this, app badges can also be set within the app, bypassing the need to rely on notifications to set the badge.

By setting up an event handler for when notifications are delivered, badges setting can be handled in-app as soon as a remote notification is delivered (this is particularly useful when a user is in the app as a notification is delivered, since notifications are not displayed in Notification Center if the app is actively in the foreground). Finally, badges can be dismissed at any point within an app, giving the developer flexibility to tailor how badges behave.

When used wisely badges can be very effective to not only alert a user of an important development relating to your app, but also focus their attention to a particular point of the app — and even guide users to a screen where that development is at. Take the following scenario of receiving a new message, where badge UI guides the user to the particular message screen:

“Badge Flow” diagram illustrating how badge UI can guide users to development of interest.

Badges can be embedded in a tab bar controller of React Navigation, or to any other component within your app. In addition, any component can set and dismiss the badge. We’ll be visiting the particular packages and APIs to do this further down.

This article will walk through how to apply badge features in React Native (and NodeJS on the server-side in relation to Apple Push Notifications) to manage badges for an app.

If you are interested in developing an inbox feature in React Native, I have published an article detailing how to develop the feature in full with the key components needed; it acts as a good prerequisite to this piece if you’d like to build badge functionality into the feature. Read the article here: React Native: Creating an In-App Inbox.

Effective badge use cases

Receiving new messages are a strong — but not only — use case for effective badge use (of course, provided messages are not spammed to the user and stay relevant with their context). Here are some others that you may wish to implement based on your app’s primary domain / purpose:

  • Live updates on service status, such as order delivery status. Use badges to represent how many new updates there are, and place the badges in-app by icons or links that take the user to that new content.
  • For educational apps, badges are useful for notifying users when they receive new grades or comments from tutors. The same can be said in the gaming domain, when trophies are unlocked for example.
  • Other common use cases include financial apps and social media apps, where badges represent critical changes to stocks and trades (perhaps tied into the user’s open orders that directly effect their portfolio) for the prior use case, and new threads or comments for the latter.

Where not to use badges

I have found myself turning off badge notifications for apps that use badges for things like promotions — which are ultimately advertisements sent directly to your device. This is annoying, and is by far the biggest way developers use badges negatively.

Another unwelcome badge scenario is to set them for things that users don’t care about — such as traffic notifications in an area I am no longer in, or asking to review something such as a journey you recently took, a product you recently bought, or a service you recently used. These distracting prompts will only deter users from your app, distracting the user’s natural usage of the app and force them to work around these interferences.

Try to avoid these scenarios when designing badge integration. Question whether a user will care about them, and better yet, trigger a positive emotional reaction from them.

Use badges sparingly for key state changes

The key to badge use is to limit badges to features that your users will care about. Using them sparingly will minimise the risk of users turning off badges altogether (that is very easy to do within Settings).

Perhaps the most popular way to set a badge is via remote notifications (or APNs) that are delivered to a user’s device, partly due to the fact that your server-side processes make changes behind the scenes and often need to bring those changes to the user’s attention when they are synced with the app on-device.

The following section will explore how to do badge management in React Native with a context provider and some useful badge APIs. We will then focus on how to manage badges in conjunction with remote APNs in a Node JS environment in the section to follow.


Badge Manager in React Native

A badge can be fetched and set within React Native with a simple API provided by the @react-native-community/push-notification-ios package. Add the package to your project via yarn:

yarn add @react-native-community/push-notification-ios

This package connects native push notification APIs with Javascript APIs, allowing us to respond to and manage notifications inside React Native.

Another package, react-native-push-notification , provides an even simpler API to @react-native-community/push-notification-ios, but uses the latter as a dependency. These two packages can work hand-in-hand for a push notification management solution talked about in my APN article.

The following API can be used to get the current badge count:

// getting the badge countimport PushNotificationIOS from '@react-native-community/push-notification-ios'PushNotificationIOS.getApplicationIconBadgeNumber(num => {
setBadges(num);
});

Note that this is not a synchronous call; a callback function that provides the num parameter as its only argument needs to be defined to handle the returned badge number.

A badge can also be set in a similar way:

// setting a badge countimport PushNotificationIOS from '@react-native-community/push-notification-ios'const val = 1;PushNotificationIOS.setApplicationIconBadgeNumber(val);

Dismissing the badge can simply be done by setting the badge number to zero:

// dismissing badgePushNotificationIOS.setApplicationIconBadgeNumber(0);

Unlike the getter, setApplicationIconBadgeNumber is synchronous and simply requires a number to apply to the badge. Setting a badge has two implications:

  • The badge will be displayed with your app icon on the device’s home screen.
  • The badge count can then be fetched from any component within React Native — it is globally accessible, not disimilar to how AsyncStorage values are globally accessible.

This is already very useful for alerting app users of new developments from inside the app, although it can be argued that the user will already know about these developments if they are triggered by a user action.

Concretely, remote push notifications that trigger badge counts are more effective at fulfilling the use cases mentioned in the article introduction, but having the ability to configure badges in-app is also a critical ability — especially when dismissing the badge count. We’ll look at updating badge count with remote notifications further down the article.

Updating state when badge count changes

Let’s now look at how the above APIs can be tied into component state updates. Updating some state that reflects the badge count will allow all components that rely on this badge count to update their UI accordingly on every change.

We can do this via React Context, whereby the context needs the following characteristics:

  • A badge state value needs to be defined, that will represent the current badge value of the app. This value can be set to 0 by default.
  • The context must provide a function to set the badge count — we’ll call this setBadge. We’ll also use a useContext hook defined as useApnBadge to let other (functional) components access setBadge, thus giving all components the ability to update the badge count. setBadge will not only call PushNotificationIOS.setApplicationIconBadgeNumber(val) to set the true badge count and update the native badge display, but also update the context badge state.
  • The context needs to check whether the badge count has changed when the app re-opens from the background to the foreground. An AppState event listener can be used to do this, thus keeping your context badge count in sync with the actual badge count.

Working with App State is very effective for keeping your app up to date by checking for external influences when the user opens it back into the foreground. It can also control classes like timers, such that timers are paused when the app goes into the background. Read more about App State in my article dedicated on the topic: Working with App State and Event Listeners in React Native.

The following gist showcases this context in its entirety. Take note of the following details:

  • useRef is being used as a way for AppState, that is a common convention for ensuring event listeners have access to the current component state (that by default they do not — event listeners will only be aware of the state of when they were first initialised).
  • The native badge count and context state badge count are being updated concurrently.
  • Event listeners are initialised after the component’s initial render (via useEffect), and are removed when the component unmounts (defined in useEffect’s return function).

With that said, here’s the gist in its entirety:

With <APNBadgeProvider /> wrapping your component tree, all your components will have access to the context, and thus will be able to access and update the badge count, triggering re-renders in the process.

Setting the badge count with an incoming remote notification

Importantly, it’s worth noting that other event listeners can utilise our badge context. If you have an event listener that listens for new remote push notifications for example, this context could indeed call setBadge to keep state badge count in sync.

As an example of doing this, the react-native-push-notification package (mentioned earlier) handles incoming notifications in it’s onNotification event handler. It is in this event handler that we can set a badge count, that can depend on the notification itself or its payload data.

The following snippet takes the boilerplate from react-native-push-notification’s Usage section within its documentation, and calls setBadge if a new message is received. The message type is determined via the payload property of the APN, that can be used to define custom data specific to the notification in question:

const { setBadges } = useApnBadge();PushNotification.configure({
onRegister: async function (tokenData) {
...
},
onNotification: function (notification: any) { // setBadge if notification is new message
try {
if (notification.payload.type === 'new_message') {
setBadge(1);
}
} catch (e) {
console.log(e);
}
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
permissions: {
alert: true,
badge: true,
sound: true,
},
...
});

The badge count is updated before the finish() method is called. Note also that the badge permission has been set to true. If you are using react-native-push-notification as your APN manager, make sure this is set so users allow receiving badges when they first register their device token.

This was quite a specific use case, and so the next section will briefly document other ways the badge context can be used for regular React components, thus keeping UI in sync.


Displaying Badges

One common location to include badges in-app is next to a tab bar controller icon. Let’s see exactly how badges can be embedded here with custom styling and positioning for a particular icon.

Badge with React Navigation Tab Bar Icon

React Navigation 5 introduced a revamped API that moved from a declarative navigator configuration to a component based one. In other words, your navigators can be entirely configured with components and props.

The following gist defines a tab bar navigator with a badge embedded in a Messages tab icon. Before the full solution, let’s check out the badge specific code. Here is the code specific to the badge itself, located in the return statement of screenOptions.tabBarIcon:

// remember we're using the context hook we defined earlier
const { badge } = useApnBadge();
...return (
<View style={styles.tabContainer}>
{(route.name === 'Messages' && badge > 0) &&
<View style={styles.tabBadge}>
<Text style={styles.tabBadgeText}>
{badge}
</Text>
</View>
}
{icon}
</View>
);

Note that the badge is only displayed if the value is greater than 0, and if the particular icon is the messages icon. The styling for the badge is an absolute positioned <View /> relative to a parent <View /> acting as a container for the icon and badge. Here are those styles, that place the badge at the top right of the icon:

import { StyleSheet } from 'react-native'export const styles = StyleSheet.create({
tabContainer: {
width: 24,
height: 24,
position: 'relative',
},
tabBadge: {
position: 'absolute',
top: -5,
right: -10,
backgroundColor: 'red',
borderRadius: 16,
paddingHorizontal: 6,
paddingVertical: 2,

zIndex: 2,
},
tabBadgeText: {
color: 'white',
fontSize: 11,
fontWeight: '600',
},
});
export default styles;

The bolded properties highlight what styles the badge in a similar way to the native badge icon.

Here is the full gist of the tab bar controller component:

That’s all there is to the idea of embedding custom badges into your components, although you are not limited to only displaying the badge itself. Here are a couple of common ways you can grab the user’s attention when there is an active badge value:

  • Have an actual message on your app’s home screen or dashboard screen letting the user know there is a new development relating to the badge. Use useApnBadge to determine whether there is an active badge value to display messages accordingly.
  • Displaying red circles at particular points of interest without the number inside the badge. This acts as a good way to guide the user to the particular point of interest.

Dismissing the badge

An equally important task to displaying the badge is to dismiss it once the user is at the desired screen. This can also be done with our useApnBadge context in conjunction with React Navigation’s useFocusEffect hook to reset the badge count upon visiting, or focussing, a screen:

// reset badge count upon visit to this screenconst { badge, setBadge } = useApnBadge();useFocusEffect(
useCallback(() => {
if(badge === 0) {
setBadge(0);
}
});

}, [])
);

Note that useCallback is also used here, adhering to the React Navigation documentation to memoize functions within useFocusEffect. To read up on React Navigation 5’s main features and the differences from the previous version, check out Get Started with React Navigation 5 in React Native.

With that, we have now covered how to implement badges on the React Native side. The last section of this piece talks about how remote push notifications can be delivered with a badge in their payload, exploring an APN implementation in Node JS and Express.


APNs with Badges Server Side

This section explores how to deliver an APN with a badge in a Node JS Express environment and apn package.

For a full breakdown of configuring Apple Push Notifications in React Native, XCode and the Apple Developer Portal, check out my article on the subject: Apple Push Notifications with React Native and Node.js.

Setting up an APN service with Node JS and Express is my preferred way to implement the service, whereby an APN endpoint can easily be configured with another route.

You can supply this route various parameters in your request body such as the APN title, message content, and other values in the payload, such as the badge number and which sound to use. This section explores some sample code to do exactly this.

There is in fact a rather popular package that provides a streamlined API to deliver APNs, and that package is apn, also termed Node APN. Simply install the package with yarn to add it to your project:

yarn add apn

Node APN works by initiating an “APN Provider” object with your APN credentials (namely your Key ID and Team ID found on the Apple Developer Portal) and calling this object’s send() method with your notification configuration.

When initiating an APN provider for your Express server, do so outside of the route itself; this only has to be done once and will stay initiated for the duration of your runtime:

// initiating an APN provider objectconst apn = require('apn')const apnProvider = new apn.Provider({
token: {
key: path.join(__dirname, '..', 'certs', 'cert.p8'),
keyId: process.env.APN_KEY_ID,
teamId: process.env.APN_TEAM_ID,
},
production: process.env.APN_ENVIRONMENT === 'production'
});

apn.Provider takes one argument being a JSON object containing your credentials and high level service configs:

  • Note that the key certificate has been included by fetching its relative path using path.join., a very useful API that recursively defines a path with its support for an arbitrary amount of parameters. The above example uses __dirname as the starting point of the path, then moves up one directory with .., before entering the certs folder and finally citing cert.p8 file.
  • Beyond your Key credentials, your Team ID credential that must be included is simply your developer account team identifier. Environment variables have been used for these sensitive data. Note that the production property expects a boolean value — the example tests whether the APN_ENVIRONMENT environment variable equates to production, that would result to true, or false otherwise.

Working with relative file paths is more suitable for multi-environment setups, such as sandbox and production environments where the absolute root folder of your project will be different, resulting in file paths breaking when working with the various environments.

With your apnProvider configured you can now send notifications. The badge property is used to configure a badge value of the notification. E.g. If a badge value of 3 is configured, then that is the value that’ll appear with your app icon. The following example object demonstrates a notification with a badge (and sound) configured, in an /apn/send route:

// configuring an Express route that handles APNsrouter.post('/apn/send`, async function (req, res, next) {  // get notification config from request body
const {
deviceTokens,
title,
message,
badge,
sound
} = req.body;
// configure notification
let body = {
alert: {
title: title,
body: message
},
topic: process.env.APP_BUNDLE_ID,
pushType: 'background',
};
if (sound !== undefined) {
body.sound = sound
}
if (badge !== undefined) {
body.badge = badge;
}
...
});

For the full list of notification options check the Convenience Setters section of apn's documentation.

This route is set up such that the notification body changes depending on the body of the request. badge and sound are optional, and therefore are only included if the request has defined values for each of them.

Now all that is left to do is wrap your body in a new apn.Notification instance and send it to Apple’s servers. The next example shows how to do this, along with dealing with failed device tokens:

// instantiate notification object
let notification = new apn.Notification(body);
// send notification asynchronously and await response
const response = await apnProvider.send(notification, deviceTokens);
// deal with bad apn tokens
if (response.failed.length > 0) {
for (let i = 0; i < response.failed.length; i++) {
const device = response.failed[i].device;
const reason = response.failed[i].response.reason;
if (reason === 'BadDeviceToken') {
// remove from user settings, log failure...
}
}
}

Although this goes slightly beyond badge configuration, it demonstrates best practices in regards to tidying up invalid device tokens. A BadDeviceToken failure will warrant you to delete the device token in question from a user’s account, thus keeping your data up to date.

With the ability to invoke badges via push notifications, let’s now turn our attention to React Native and the app itself. Although this aforementioned badge use case is vital, you can also invoke the badge inside the app itself. This could be in response to an API request when syncing your app, or when a new event from a live socket connection comes in — the next section explores how to do this efficiently in React Native.


In Summary

This article has explored how notification badges can be integrated in a React Native project, and how to use badges to update component state and other UX that guides the user to the relevant section of your app pertaining to these updates.

The APIs needed to get and set badges has been showcased, as well as advanced React techniques to keep the app in sync via event listeners and context. We also explored how badges are sent via remote push notifications, and walked through a scenario where a NodeJS Express service is delivering those notification payloads to app users.

More articles relating to the topics of this piece will be added here as and when they are published.

Written by

Programmer and Author. Director @ JKRBInvestments.com. Creator of ChineseGrammarReview.app for iOS.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store