Skip to main content

Server Integration

The Cloud Messaging module provides the tools required to enable you to send custom messages directly from your own servers. For example, you could send an FCM message to a specific device when a new chat message is saved to your database and display a notification, or update local device storage, so the message is instantly available.

Firebase provides a number of SDKs in different languages such as Node.JS, Java, Python, C# and Go. It also supports sending messages over HTTP. These methods allow you to send messages directly to your user's devices via the FCM servers.

Device tokens#

To send a message to a device, you must access its unique token. A token is automatically generated by the device and can be accessed using the Cloud Messaging module. The token should be saved inside your systems data-store and should be easily accessible when required.

The examples below use a Cloud Firestore database to store and manage the tokens, and Firebase Authentication to manage the users identity. You can however use any datastore or authentication method of your choice.

If using iOS, ensure you have completed the setup & requested user permission before trying to receive messages!

Saving tokens#

Once your application has started, you can call the getToken method on the Cloud Messaging module to get the unique device token (if using a different push notification provider, such as Amazon SNS, you will need to call getAPNSToken on iOS):

Future<void> saveTokenToDatabase(String token) async {
// Assume user is logged in for this example
String userId = FirebaseAuth.instance.currentUser.uid;
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.update({
'tokens': FieldValue.arrayUnion([token]),
});
}
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String _token;
Future<void> setupToken() async {
// Get the token each time the application loads
String? token = await FirebaseMessaging.instance.getToken();
// Save the initial token to the database
await saveTokenToDatabase(token!);
// Any time the token refreshes, store this in the database too.
FirebaseMessaging.instance.onTokenRefresh.listen(saveTokenToDatabase);
}
@override
void initState() {
super.initState();
setupToken();
}
@override
Widget build(BuildContext context) {
return Text("...");
}
}

The above code snippet has a single purpose; storing the device FCM token on a remote database. When your application first initializes, the users FCM token is fetched and stored in a database (Cloud Firestore in this example). If the token is refreshed at any point whilst your application is open, the new token is also stored on the database.

It is important to remember a user can have many tokens (from multiple devices, or token refreshes), therefore we use FieldValue.arrayUnion to store new tokens. When a message is sent via an admin SDK, invalid/expired tokens will throw an error allowing you to then remove them from the database.

Using tokens#

With the tokens stored in a secure datastore, we now have the ability to send messages via FCM to those devices.

The following example uses the Node.JS firebase-admin package to send messages to our devices, however any Firebase Admin SDK can be used.

Imagine our application being similar to Instagram. Users are able to upload pictures, and other users can "like" those pictures. Each time a post is liked, we want to send a message to the user that uploaded the picture.

The code below simulates a function which is called with all the information required when a picture is liked:

// Node.js e.g via a Firebase Cloud Function
var admin = require("firebase-admin");
// ownerId - who owns the picture someone liked
// userId - id of the user who liked the picture
// picture - metadata about the picture
async function onUserPictureLiked(ownerId, userId, picture) {
// Get the owners details
const owner = admin.firestore().collection("users").doc(ownerId).get();
// Get the users details
const user = admin.firestore().collection("users").doc(userId).get();
await admin.messaging().sendToDevice(
owner.tokens, // ['token_1', 'token_2', ...]
{
data: {
owner: JSON.stringify(owner),
user: JSON.stringify(user),
picture: JSON.stringify(picture),
},
},
{
// Required for background/quit data-only messages on iOS
contentAvailable: true,
// Required for background/quit data-only messages on Android
priority: "high",
}
);
}

Data-only messages are sent as low priority on both Android and iOS and will not trigger the background handler by default. To enable this functionality, you must set the "priority" to high on Android and enable the content-available flag for iOS in the message payload.

The data property can send an object of key-value pairs totaling 4 KB as string values (hence the JSON.stringify calls).

Within the application, you can then handle these messages how you see fit:

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
Map<String, String> data = message.data;
Owner owner = Owner.fromMap(jsonDecode(data['owner']));
User user = User.fromMap(jsonDecode(data['user']));
Picture picture = Picture.fromMap(jsonDecode(data['picture']));
print('The user ${user.name} liked your picture "${picture.title}"!');
});

Your application code can then handle messages as you see fit; updating local cache, displaying a notification or updating UI. The possibilities are endless!

Send messages to topics#

When devices subscribe to topics, you can send messages without specifying/storing any device tokens.

Using the firebase-admin Admin SDK as an example, we can send a message to devices subscribed to a topic:

// Node.js e.g via a Firebase Cloud Function
const admin = require("firebase-admin");
const message = {
data: {
type: "warning",
content: "A new weather warning has been created!",
},
topic: "weather",
};
admin
.messaging()
.send(message)
.then((response) => {
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});

Conditional topics#

To send a message to a combination of topics, specify a condition, which is a boolean expression that specifies the target topics. For example, the following condition will send messages to devices that are subscribed to weather and either news or traffic:

const admin = require("firebase-admin");
const message = {
data: {
content: "New updates are available!",
},
condition: "'weather' in topics && ('news' in topics || 'traffic' in topics)",
};
admin
.messaging()
.send(message)
.then((response) => {
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});