In this tutorial, you will learn:
- How to display a local notification when the app is both in background and foreground.
- How to send push notifications in React Native using Expo.
- How to send push notifications from one phone to another.
- To build a back-end to send push notifications to your users.
Local Notifications
You might have come across apps which will display notification locally, such as a reminder app.
These notifications are called local notifications since they are sent from within the app.
First, let's learn how to display a local notification in our app and
later we will make use of these local notifications to display push notifications.
Creating the Project
Create a new Expo project using the following command.
While creating the project, it would ask you to select a template. Choose "blank" template.
expo init react-native-push-notifications
In order to show notifications, we need to install the package expo-notifications
.
Also, in iOS we need explicit permission from the user to display notifications.
The package expo-permissions
is used to handle permissions. So let's install both of them by running the following command:
expo install expo-notifications expo-permissions
Now open app.json
and add "useNextNotificationsApi": true
for enabling notifications in Android:
{
"expo": {
"name": "react-native-push-notifications",
"slug": "react-native-push-notifications",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"useNextNotificationsApi": true
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Update the App.js
with the following code:
import React, { useEffect } from "react"
import { StyleSheet, View, Button } from "react-native"
import * as Notifications from "expo-notifications"
import * as Permissions from "expo-permissions"
// Show notifications when the app is in the foreground
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
}
},
})
export default function App() {
useEffect(() => {
// Permission for iOS
Permissions.getAsync(Permissions.NOTIFICATIONS)
.then(statusObj => {
// Check if we already have permission
if (statusObj.status !== "granted") {
// If permission is not there, ask for the same
return Permissions.askAsync(Permissions.NOTIFICATIONS)
}
return statusObj
})
.then(statusObj => {
// If permission is still not given throw error
if (statusObj.status !== "granted") {
throw new Error("Permission not granted")
}
})
.catch(err => {
return null
})
}, [])
const triggerLocalNotificationHandler = () => {
Notifications.scheduleNotificationAsync({
content: {
title: "Local Notification",
body: "Hello this is a local notification!",
},
trigger: { seconds: 5 },
})
}
return (
<View style={styles.container}>
<Button
title="Trigger Local Notification"
onPress={triggerLocalNotificationHandler}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
})
Displaying Notifications when the App is in the Foreground
By default, notifications will be displayed only when the app is running in the background.
But there will be scenarios where you would want to show the notification when the app is running in the foreground,
like informing the user about the completion of a task or reminding them to do something.
To enable the notifications when the app is in the foreground,
we call setNotificationHandler
function with a handleNotification
callback and pass shouldShowAlert
as true
Requesting Permission in iOS
In iOS, you need explicit permission from the user to show the notification.
For that, we call Permissions.getAsync(Permissions.NOTIFICATIONS)
to check if we already have the permission.
If we do not have the permission, then we call Permissions.askAsync(Permissions.NOTIFICATIONS)
to get permission from the user.
If the user does not provide the permission, then we throw an error.
However, we are not handling the error in the catch block in our code.
You may show an alert to the user telling them they need to provide the permission.
Scheduling the Notification
When the button is pressed, we call Notifications.scheduleNotificationAsync
inside triggerLocalNotificationHandler
function.
It takes an object as an argument with properties content
and trigger
.
-
content
- We can pass the notificationtitle
andbody
inside the content. You can check out the other properties here. -
trigger
- Tells when to show the notification. You can specify an absolute time or a relative time. In our case, we are specifying a relative time of 5 seconds from now. If you want the notification to be shown immediately, you can pass thetrigger
asnull
.
To repeat the notification, you can use the repeat
property as shown below:
Notifications.scheduleNotificationAsync({
content: {
title: 'Remember to drink water!,
},
trigger: {
seconds: 60 * 20,
repeats: true
},
});
You can read about other ways of using the trigger input here.
Now if you run the app in iOS, you will be asked for permission:
If you press the button, should be able to see the notification after 5 seconds:
Handling Received Notifications
You can subscribe to the received notification by passing a callback to Notifications.addNotificationReceivedListener
and
add a function to be called when the notification is clicked by passing it to Notifications.addNotificationResponseReceivedListener
as shown below:
import React, { useEffect } from "react"
import { StyleSheet, View, Button } from "react-native"
import * as Notifications from "expo-notifications"
import * as Permissions from "expo-permissions"
// Show notifications when the app is in the foreground
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
}
},
})
export default function App() {
useEffect(() => {
// Permission for iOS
Permissions.getAsync(Permissions.NOTIFICATIONS)
.then(statusObj => {
// Check if we already have permission
if (statusObj.status !== "granted") {
// If permission is not there, ask for the same
return Permissions.askAsync(Permissions.NOTIFICATIONS)
}
return statusObj
})
.then(statusObj => {
// If permission is still not given throw error
if (statusObj.status !== "granted") {
throw new Error("Permission not granted")
}
})
.catch(err => {
return null
})
}, [])
useEffect(() => {
const receivedSubscription = Notifications.addNotificationReceivedListener(
notification => {
console.log("Notification Received!")
console.log(notification)
}
)
const responseSubscription = Notifications.addNotificationResponseReceivedListener(
response => {
console.log("Notification Clicked!")
console.log(response)
}
)
return () => {
receivedSubscription.remove()
responseSubscription.remove()
}
}, [])
const triggerLocalNotificationHandler = () => {
Notifications.scheduleNotificationAsync({
content: {
title: "Local Notification",
body: "Hello this is a local notification!",
},
trigger: { seconds: 5 },
})
}
return (
<View style={styles.container}>
<Button
title="Trigger Local Notification"
onPress={triggerLocalNotificationHandler}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
})
Push Notification
To send push notification to a device through Expo, we need to know the push token of that device.
We can fetch the push token by calling Notifications.getExpoPushTokenAsync
function:
import React, { useEffect } from "react"
import { StyleSheet, View, Button } from "react-native"
import * as Notifications from "expo-notifications"
import * as Permissions from "expo-permissions"
// Show notifications when the app is in the foreground
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
}
},
})
export default function App() {
useEffect(() => {
// Permission for iOS
Permissions.getAsync(Permissions.NOTIFICATIONS)
.then(statusObj => {
// Check if we already have permission
if (statusObj.status !== "granted") {
// If permission is not there, ask for the same
return Permissions.askAsync(Permissions.NOTIFICATIONS)
}
return statusObj
})
.then(statusObj => {
// If permission is still not given throw error
if (statusObj.status !== "granted") {
throw new Error("Permission not granted")
}
})
.then(() => {
return Notifications.getExpoPushTokenAsync()
})
.then(response => {
const deviceToken = response.data
console.log({ deviceToken })
})
.catch(err => {
return null
})
}, [])
useEffect(() => {
const receivedSubscription = Notifications.addNotificationReceivedListener(
notification => {
console.log("Notification Received!")
console.log(notification)
}
)
const responseSubscription = Notifications.addNotificationResponseReceivedListener(
response => {
console.log("Notification Clicked!")
console.log(response)
}
)
return () => {
receivedSubscription.remove()
responseSubscription.remove()
}
}, [])
const triggerLocalNotificationHandler = () => {
Notifications.scheduleNotificationAsync({
content: {
title: "Local Notification",
body: "Hello this is a local notification!",
},
trigger: { seconds: 5 },
})
}
return (
<View style={styles.container}>
<Button
title="Trigger Local Notification"
onPress={triggerLocalNotificationHandler}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
})
If you run the application now in the emulator, you will be able to see the token printed in the terminal.
Now you can send push notifications to the emulator using the expo notifications tool,
just by providing the title, message and Expo push token.
Push notifications are not supported in the iOS emulator. It needs to be tested in a real device.
Sending notification from one device to another
If you have to send notification from one device to another you can do so by posting it to the expo endpoint as shown in the code below:
import React, { useEffect, useState } from "react"
import { Button, StyleSheet, View, TextInput } from "react-native"
import * as Notifications from "expo-notifications"
import * as Permissions from "expo-permissions"
// Show notifications when the app is in the foreground
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
}
},
})
export default function App() {
const [title, setTitle] = useState()
const [body, setBody] = useState()
const [token, setToken] = useState()
useEffect(() => {
// Permission for iOS
Permissions.getAsync(Permissions.NOTIFICATIONS)
.then(statusObj => {
// Check if we already have permission
if (statusObj.status !== "granted") {
// If permission is not there, ask for the same
return Permissions.askAsync(Permissions.NOTIFICATIONS)
}
return statusObj
})
.then(statusObj => {
// If permission is still not given throw error
if (statusObj.status !== "granted") {
throw new Error("Permission not granted")
}
})
.then(() => {
return Notifications.getExpoPushTokenAsync()
})
.then(response => {
const deviceToken = response.data
console.log({ deviceToken })
})
.catch(err => {
return null
})
}, [])
useEffect(() => {
const receivedSubscription = Notifications.addNotificationReceivedListener(
notification => {
console.log("Notification Received!")
console.log(notification)
}
)
const responseSubscription = Notifications.addNotificationResponseReceivedListener(
response => {
console.log("Notification Clicked!")
console.log(response)
}
)
return () => {
receivedSubscription.remove()
responseSubscription.remove()
}
}, [])
const triggerLocalNotificationHandler = () => {
Notifications.scheduleNotificationAsync({
content: {
title: "Local Notification",
body: "Hello this is a local notification!",
},
trigger: { seconds: 5 },
})
}
const triggerPushNotificationHandler = () => {
fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: {
Accept: "application/json",
"Accept-Encoding": "gzip,deflate",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: token,
title,
body,
}),
})
}
return (
<View style={styles.container}>
<Button
title="Trigger Local Notification"
onPress={triggerLocalNotificationHandler}
/>
<TextInput
style={styles.textInput}
value={title}
placeholder="Title"
onChangeText={setTitle}
/>
<TextInput
style={styles.textInput}
value={body}
placeholder="Body"
onChangeText={setBody}
/>
<TextInput
style={styles.textInput}
value={token}
placeholder="Token"
onChangeText={setToken}
/>
<Button
title="Trigger Push Notification"
onPress={triggerPushNotificationHandler}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
textInput: {
borderBottomWidth: 1,
padding: 5,
margin: 15,
width: "80%",
},
})
Here we have added 3 input boxes for entering notification title, notification body, and device token and bound them with local states.
When the button is pressed, the expo API is called with these details.
Building a custom notification tool
Since we require the device token of the user to send notifications, we need to store them somewhere so that we can use them in the future.
So, let's build a back-end, to where we can post the user token to save it to the database and a UI to retrieve the tokens and send the notifications.
Notification API back-end
Create a Node.js project using the following command:
npm init react-native-push-api
Update the package.json
as shown below:
{
"name": "react-native-push-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"expo-server-sdk": "^3.6.0",
"express": "^4.17.1",
"mongoose": "^5.12.3"
}
}
Run npm install
to install the dependencies mentioned above.
If you see, we are using expo-server-sdk to help in sending the notification to the expo server.
We are using MongoDB for storing the tokens. You may use either a local instance or
connect to a cloud provider like MongoDB Atlas.
Now let's create a .env file and store the configurations there
MONGO_DB_CONNECTION_STRING = mongodb://127.0.0.1:27017/react_native_push
WHITELISTED_DOMAINS = http://localhost:3000
WHITELISTED_DOMAINS
will be used for CORS whitelisting of the client.
Now create a folder named utils
and create a file called connectdb.js
which helps in connect to the database:
const mongoose = require("mongoose")
const url = process.env.MONGO_DB_CONNECTION_STRING
const connect = mongoose.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
connect
.then(db => {
console.log("connected to db")
})
.catch(err => {
console.log(err)
})
Create another file named expo.js
with the following code.
The code is taken as-is from the expo-server-sdk GitHub page.
const { Expo } = require("expo-server-sdk")
module.exports = (pushTokens, title, body, data) => {
// Create a new Expo SDK client
// optionally providing an access token if you have enabled push security
let expo = new Expo({ accessToken: process.env.EXPO_ACCESS_TOKEN })
// Create the messages that you want to send to clients
let messages = []
for (let pushToken of pushTokens) {
// Each push token looks like ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
// Check that all your push tokens appear to be valid Expo push tokens
if (!Expo.isExpoPushToken(pushToken)) {
console.error(`Push token ${pushToken} is not a valid Expo push token`)
continue
}
// Construct a message (see https://docs.expo.io/push-notifications/sending-notifications/)
messages.push({
to: pushToken,
sound: "default",
title,
body,
data,
})
}
// The Expo push notification service accepts batches of notifications so
// that you don't need to send 1000 requests to send 1000 notifications. We
// recommend you batch your notifications to reduce the number of requests
// and to compress them (notifications with similar content will get
// compressed).
let chunks = expo.chunkPushNotifications(messages)
let tickets = []
;(async () => {
// Send the chunks to the Expo push notification service. There are
// different strategies you could use. A simple one is to send one chunk at a
// time, which nicely spreads the load out over time:
for (let chunk of chunks) {
try {
let ticketChunk = await expo.sendPushNotificationsAsync(chunk)
console.log(ticketChunk)
tickets.push(...ticketChunk)
// NOTE: If a ticket contains an error code in ticket.details.error, you
// must handle it appropriately. The error codes are listed in the Expo
// documentation:
// https://docs.expo.io/push-notifications/sending-notifications/#individual-errors
} catch (error) {
console.error(error)
}
}
})()
// Later, after the Expo push notification service has delivered the
// notifications to Apple or Google (usually quickly, but allow the the service
// up to 30 minutes when under load), a "receipt" for each notification is
// created. The receipts will be available for at least a day; stale receipts
// are deleted.
//
// The ID of each receipt is sent back in the response "ticket" for each
// notification. In summary, sending a notification produces a ticket, which
// contains a receipt ID you later use to get the receipt.
//
// The receipts may contain error codes to which you must respond. In
// particular, Apple or Google may block apps that continue to send
// notifications to devices that have blocked notifications or have uninstalled
// your app. Expo does not control this policy and sends back the feedback from
// Apple and Google so you can handle it appropriately.
let receiptIds = []
for (let ticket of tickets) {
// NOTE: Not all tickets have IDs; for example, tickets for notifications
// that could not be enqueued will have error information and no receipt ID.
if (ticket.id) {
receiptIds.push(ticket.id)
}
}
let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds)
;(async () => {
// Like sending notifications, there are different strategies you could use
// to retrieve batches of receipts from the Expo service.
for (let chunk of receiptIdChunks) {
try {
let receipts = await expo.getPushNotificationReceiptsAsync(chunk)
console.log(receipts)
// The receipts specify whether Apple or Google successfully received the
// notification and information about an error, if one occurred.
for (let receiptId in receipts) {
let { status, message, details } = receipts[receiptId]
if (status === "ok") {
continue
} else if (status === "error") {
console.error(
`There was an error sending a notification: ${message}`
)
if (details && details.error) {
// The error codes are listed in the Expo documentation:
// https://docs.expo.io/push-notifications/sending-notifications/#individual-errors
// You must handle the errors appropriately.
console.error(`The error code is ${details.error}`)
}
}
}
} catch (error) {
console.error(error)
}
}
})()
}
Now create a folder named models
and create a file called token.js
inside it:
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const Token = new Schema({
tokenValue: {
type: String,
default: "",
},
})
module.exports = mongoose.model("Token", Token)
Here we are creating Token model to store the token value in the database.
Finally, create the index.js
file and update it with the following code:
const express = require("express")
const cors = require("cors")
const bodyParser = require("body-parser")
const sendPushNotification = require("./utils/expo")
if (process.env.NODE_ENV !== "production") {
// Load environment variables from .env file in non prod environments
require("dotenv").config()
}
require("./utils/connectdb")
const Token = require("./models/token")
const app = express()
app.use(bodyParser.json())
// Add the client URL to the CORS policy
const whitelist = process.env.WHITELISTED_DOMAINS
? process.env.WHITELISTED_DOMAINS.split(",")
: []
const corsOptions = {
origin: function (origin, callback) {
if (!origin || whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error("Not allowed by CORS"))
}
},
credentials: true,
}
app.use(cors(corsOptions))
app.get("/", function (req, res) {
res.send({ status: "success" })
})
app.post("/send_notification", function (req, res) {
const { title, body, data, to } = req.body
if (to === "all") {
Token.find({}, (err, allTokens) => {
if (err) {
res.statusCode = 500
res.send(err)
}
const tokens = allTokens.map(token => {
return token.tokenValue
})
sendPushNotification(tokens, title, body, data)
res.send({ status: "success" })
})
} else {
sendPushNotification([to], title, body, data)
res.send({ status: "success" })
}
})
app.post("/save_token", function (req, res) {
const token = req.body.token
if (token) {
Token.find({ tokenValue: token }, (err, existingToken) => {
if (err) {
res.statusCode = 500
res.send(err)
}
if (!err && existingToken.length === 0) {
const newToken = new Token({ tokenValue: req.body.token })
newToken.save(function (err, savedToken) {
if (err) {
res.statusCode = 500
res.send(err)
}
res.send({ status: "success" })
})
} else {
res.send({ status: "success" })
}
})
} else {
res.statusCode = 400
res.send({ message: "token not passed!" })
}
})
app.get("/all_tokens", function (req, res) {
Token.find({}, (err, allTokens) => {
if (err) {
res.statusCode = 500
res.send(err)
}
res.send(
allTokens.map(token => {
// remove unnecessary fields
return { value: token.tokenValue }
})
)
})
})
// Start the server in port 8081
const server = app.listen(process.env.PORT || 8081, function () {
const port = server.address().port
console.log("App started at port:", port)
})
Here we are having 3 routes:
- To send a notification to all the devices or a single device
- To save the device token to the database
- To fetch all the tokens from the database.
You may test it using postman. I have deployed it to Heroku and will be using that endpoint when we build the UI.
Building the notification console UI
Let's now go ahead and create a React Project to have a UI to send notifications using the API we have built.
Run the following command to create a new React project:
npx create-react-app push-api-client
Install BlueprintJS to style the page:
yarn add @blueprintjs/core
Now import BlueprintJS css in index.css
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
body {
margin: 0 auto;
max-width: 400px;
}
Now update the App.js
with the following code:
import {
Card,
Button,
FormGroup,
InputGroup,
H2,
TextArea,
Intent,
HTMLSelect,
Toaster,
Position,
} from "@blueprintjs/core"
import React, { useEffect, useRef, useState } from "react"
const allOption = [{ value: "all", label: "All" }]
// Replace it with your own endpoint
const API_ENDPOINT = "https://react-native-push-api.herokuapp.com/"
function App() {
const [title, setTitle] = useState("")
const [body, setBody] = useState("")
const [data, setData] = useState()
const [recipients, setRecipients] = useState(allOption)
const [to, setTo] = useState("all")
const [isSubmitting, setIsSubmitting] = useState(false)
const toastRef = useRef()
useEffect(() => {
fetch(API_ENDPOINT + "all_tokens").then(async response => {
if (response.ok) {
const tokens = await response.json()
setRecipients(allOption.concat(tokens))
}
})
}, [])
const formSubmitHandler = e => {
let parsedData = {}
try {
parsedData = data ? JSON.parse(data) : {}
} catch (err) {
console.log(err)
}
e.preventDefault()
setIsSubmitting(true)
fetch(API_ENDPOINT + "send_notification", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to,
title,
body,
data: parsedData,
}),
})
.then(async response => {
setIsSubmitting(false)
if (response.ok) {
toastRef.current.show({
icon: "tick",
intent: Intent.SUCCESS,
message: "Notification sent successfully.",
})
} else {
toastRef.current.show({
icon: "warning-sign",
intent: Intent.DANGER,
message: "Something went wrong.",
})
}
})
.catch(err => {
setIsSubmitting(false)
toastRef.current.show({
icon: "warning-sign",
intent: Intent.DANGER,
message: "Something went wrong.",
})
})
}
return (
<Card elevation="1">
<Toaster
ref={toastRef}
autoFocus={false}
canEscapeKeyClear={true}
position={Position.TOP}
usePortal={true}
/>
<H2>Send Push Notification</H2>
<form className="notification-form" onSubmit={formSubmitHandler}>
<FormGroup label="Notification Title" labelFor="title">
<InputGroup
id="title"
placeholder="Notification Title"
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
/>
</FormGroup>
<FormGroup label="Notification Body" labelFor="body">
<InputGroup
id="body"
placeholder="Notification Body"
type="text"
value={body}
onChange={e => setBody(e.target.value)}
/>
</FormGroup>
<FormGroup label="Additional Data" labelFor="data">
<TextArea
growVertically={true}
large={true}
placeholder="Additional data in JSON"
id="data"
value={data}
onChange={e => setData(e.target.value)}
className="bp3-fill"
/>
</FormGroup>
<FormGroup label="Send To" labelFor="data">
<HTMLSelect
fill
options={recipients}
value={to}
onChange={e => setTo(e.target.value)}
/>
</FormGroup>
<Button
intent="primary"
fill
type="submit"
text={isSubmitting ? "Sending" : "Send"}
/>
</form>
</Card>
)
}
export default App
Now if you run the application, you should see a page as shown below:
Before testing the console, let's add code to send the device token from the App to the backend API.
In the Expo project, update the App.js
as shown below:
import React, { useEffect, useState } from "react"
import { Button, StyleSheet, View, TextInput } from "react-native"
import * as Notifications from "expo-notifications"
import * as Permissions from "expo-permissions"
// Show notifications when the app is in the foreground
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
}
},
})
export default function App() {
const [title, setTitle] = useState()
const [body, setBody] = useState()
const [token, setToken] = useState()
useEffect(() => {
// Permission for iOS
Permissions.getAsync(Permissions.NOTIFICATIONS)
.then(statusObj => {
// Check if we already have permission
if (statusObj.status !== "granted") {
// If permission is not there, ask for the same
return Permissions.askAsync(Permissions.NOTIFICATIONS)
}
return statusObj
})
.then(statusObj => {
// If permission is still not given throw error
if (statusObj.status !== "granted") {
throw new Error("Permission not granted")
}
})
.then(() => {
return Notifications.getExpoPushTokenAsync()
})
.then(response => {
const deviceToken = response.data
fetch("https://react-native-push-api.herokuapp.com/save_token", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
token: deviceToken,
}),
})
.then(() => {
console.log("Token saved!")
})
.catch(err => {
console.log(err)
})
})
.catch(err => {
return null
})
}, [])
useEffect(() => {
const receivedSubscription = Notifications.addNotificationReceivedListener(
notification => {
console.log("Notification Received!")
console.log(notification)
}
)
const responseSubscription = Notifications.addNotificationResponseReceivedListener(
response => {
console.log("Notification Clicked!")
console.log(response)
}
)
return () => {
receivedSubscription.remove()
responseSubscription.remove()
}
}, [])
const triggerLocalNotificationHandler = () => {
Notifications.scheduleNotificationAsync({
content: {
title: "Local Notification",
body: "Hello this is a local notification!",
},
trigger: { seconds: 5 },
})
}
const triggerPushNotificationHandler = () => {
fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: {
Accept: "application/json",
"Accept-Encoding": "gzip,deflate",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: token,
title,
body,
}),
})
}
return (
<View style={styles.container}>
<Button
title="Trigger Local Notification"
onPress={triggerLocalNotificationHandler}
/>
<TextInput
style={styles.textInput}
value={title}
placeholder="Title"
onChangeText={setTitle}
/>
<TextInput
style={styles.textInput}
value={body}
placeholder="Body"
onChangeText={setBody}
/>
<TextInput
style={styles.textInput}
value={token}
placeholder="Token"
onChangeText={setToken}
/>
<Button
title="Trigger Push Notification"
onPress={triggerPushNotificationHandler}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
textInput: {
borderBottomWidth: 1,
padding: 5,
margin: 15,
width: "80%",
},
})
Now if you run the App on few devices, you will see the tokens being saved to the database and displayed in the dropdown:
Now if you test the console and send the notification, you will see the devices getting the notification:
We are not having any authentication in place for the API endpoint.
When you implement it in production, you should have authentication of some kind so that the endpoint is not spammed or misused.You could use additional security by enabling Access Tokens
so that even if the device token is stolen, it wouldn't be possible to send notifications without the access token.
Demo and Source code
You can see a demo of the Console here, the source code of the UI here, and the back-end API here.
App demo
You can see the app demo here.
Top comments (0)