Setting up an expo managed project (not applicable to expo go) to receive push notifications using FCM with custom sound on IOS and Android
- You can not follow this guide if you use Expo GO
- This is tested against a custom development client with managed workflow
- You are migrating an app to react-native that already has the server side configured to send push notifications using FCM
- You would like to use custom notification sounds which for now is not covered by Expo Push API as per their docs as of 8 Jul 2022
- You would like to fine tune the notification more than what Expo Push API provides
This guide is only meant to explain the setup needed on both android and ios to receive push notifications using Firebase Cloud Messaging. This guide replace relying on Expo to send your notifications. Even though we will not be using Expo Push Api to send notifications, we will be using expo's notification library, and eas services, which I am very thankful for and apprieciate their hard work.
While using Expo push api is simple and convenient, you may need this guide for the following reasons:
First we will need to install expo-notifications as a dependancy in our expo project.
expo install expo-notifications
in the root of your project
You can add two assets that are related to notifications:
- sounds
- recommended
.wav
sound files as per Expo docs
- recommended
- icons
- size: 96*96
- png format
- all white with transparency
- This medium blog has good explanation of android notification icons
To follow along with this guide, add a notification_sound.wav
and notification_icon.png
to your assets folder
In order for the assets in step 2 to be bundled we will use the config plugin provided by expo-notifications
package we installed in step 1
Inside app.json, inside the expo object, create a plugins key with an object such as:
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/notification_icon.png",
"sounds": ["./assets/notfication_sound.wav"],
"color": "#E87EA1"
}
]
]
You can refer to the documentation by expo
-
Go to the firebase console and create a new project.
-
add an android app
- download google-services.json
- save it in the root of your expo project
- add the path to google-services.json as a value for expo.android.googleServicesFile in app.json
-
add an ios app
- download GoogleServices-Info.plist
- save it in the root of your expo project
- add the path to GoogleServices-Info.plist as a value for expo.ios.googleServicesFile in app.json
-
Go to the Firebase console
-
Click the gear icon on the top right > project settings > General
-
Scroll down to Your apps section and select ios
Make sure you fill out the bundle id and Team Id
-
Navigate to Cloud Messaging from the top bar and scroll down to Apple App configuration
-
Make sure you add in the APNs Authentication key(should have an apple developer account)
I am using eas services by expo, but you are free to build it locally
In your terminal: run eas build -p android -profile development
, let the eas cli guide you through it until you get a dummy app.
-
navigate to :
https://expo.dev/accounts/<expo account name>/projects/<project name>/credentials/android/<android bundle identifier>
-
Scroll down to server credentials(FCM server key)
-
Press on Add a FCM Server Key
- Open your project's firebase console again
- At the top of the sidebar, click the gear icon to the right of Project Overview to go to your project settings.
- Click on the Cloud Messaging tab in the Settings pane.
- Copy the token listed next to Server key.
Note: Server Key is only available in Cloud Messaging API (Legacy), which may be Disabled by default. Enable it by clicking the 3-dot menu > Manage API in Google Cloud Console and follow the flow there. Once the legacy messaging API is enabled, you should see Server Key in that section.
-
Add as google cloud messaging token and save.
Just code a simple app that requests for the device push token and displays it:
export default function App() {
const [devicePushToken, setDevicePushToken] = useState<string | null>(null);
useEffect(() => {
registerForPushNotificationAsync().then(setDevicePushToken);
}, []);
return (
<View style={styles.container}>
<Text onPress={() => console.log(devicePushToken)}>{devicePushToken === null ? "nothing yet" : devicePushToken}</Text>
</View>
);
}
async function registerForPushNotificationAsync(): Promise<string | null> {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
if (existingStatus === "denied") {
return null;
} else if (existingStatus === "undetermined") {
const { status } = await Notifications.requestPermissionsAsync();
if (status !== "granted") {
return null;
}
}
const token = (await Notifications.getDevicePushTokenAsync()).data as string;
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
sound: "notfication_sound.wav",
});
}
return token;
}
In order to be able to send notifications using FCM, we should get hold of an authorization token. To do that do the following or check the documentation if you prefer:
-
Download a service-account file
- open up your firebase project
- Click on the gear icon (top left of the screen) > project settings > service accounts
- click on generate a new key (let it download and save it somewhere)
-
write server side code to generate the token
-
create a new server-side project (nodejs in my case) and write the equivalent of this code:
import { google } from "googleapis"; const MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging"; const SCOPES = [MESSAGING_SCOPE]; async function getAccessToken() { try { const key = require("../service-acount.json"); const jwtClient = new google.auth.JWT(key.client_email, undefined, key.private_key, SCOPES, undefined); const tokens = await jwtClient.authorize(); console.log("access_token: ", tokens.access_token); } catch (error) { console.log("an error occured"); } } getAccessToken();
make sure the downloaded service account file is corrrectly referenced
-
get the access token from this function and save it somewhere
-
###Step 10: Send Notifications
pasted from postman, feel free to refactor
```
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Bearer <access-token-acquired-in previous-step>");
var raw = JSON.stringify({
"message": {
"token": "<NATIVE-DEVICE-PUSH-TOKEN>",
"android": {
"priority": "HIGH",
"notification": {
"title": "actual title",
"body": "here is a body",
"icon": "notification_icon", // base name of icon file configured in app.json
"color": "#ff0000", // color that overlays icon in notification tray
"sound": "notification_sound",
"channelId": "default"
}
}
}
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("https://fcm.googleapis.com/v1/projects/notification-test-520e2/messages:send", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
```
In step 8 we wrote the code that returns the device push token; however, FCM will not accept the token, called APNs token, returned by Ios device.
What we will do is give Firebase the APNs token and it will give us an FCM token. To do that we can do the following request:
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "key=<FCM-Server-Key>");
var raw = JSON.stringify({
"application": "<IOS-bundle-identifier>",
"sandbox": false,
"apns_tokens": [
"<IOS-Device-APNs-token>"
]
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("https://iid.googleapis.com/iid/v1:batchImport", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
it will return a response similar to the following:
{
"results": [
{
"registration_token": "<FCM-Token-for-ios-device>",
"apns_token": "f5770663f831290b4eac97e6dfe1b388f100ed895b60775f9a64d7248b883b40",
"status": "OK"
}
]
}
Finally to send the notification to ios:
{
"message":{
"token":"<FCM-Token-for-ios-device>",
"apns": {
"payload": {
"aps": {
"alert": {
"title": "This is the title",
"subtitle": "subtitle goes here",
"body": "the bodyyyyy"
},
"badge": 1,
"sound": "notfication_sound.wav"
}
}
}
}
}
you can customize it to your liking, check out other fields from docs
If this helped, Don't forget to give it a star 🌟