Automating Push Notifications in Flutter with Firebase Cloud Functions and OneSignal.
Tags: flutter, cloudfunctions, firebase, onesignal
Published: 2021-06-06 21:24:31

Cover Photo by <a href="https://unsplash.com/@jamie452?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Jamie Street</a> on <a href="https://unsplash.com/s/photos/notifications?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>

Check my [previous article](https://dev.to/lordlamee/implementing-push-notifications-with-flutter-and-onesignal-part-1-3690) to learn how to set up OneSignal in Flutter.

Your database is going to get updated from time to time, and most times when they do, you want to inform your users about this change through push notifications.

What do you do when you need to send notifications to hundreds of users based on an update to your FireStore collection?

Lucky for us, OneSignal has an easy-to-use API that can be integrated with Firebase Cloud functions.

Enough of the talks, let's automate our push notifications!

*Requirements*
1. You must have set up firebase authentication in your Flutter app.

If you have not set up authentication in your app, check out the [official FlutterFire documentation on Firebase Authentication with Flutter](https://firebase.flutter.dev/docs/auth/usage/) to get started. 

*Step 1*
OneSignal provides unique `player IDs` to identify different devices. You need to save the player IDs of every user that registers on your app.

1. Create a Firestore collection named 'onesignalids' where you would save the player IDs for each user.

2. Add two fields to the collection.
   (i) The `customer_id` field where you save the user ID of a particular user.
   (ii) An array of strings where you save the player ID of that user.

3. Use the `savePlayerId` method to save the user's player id. This method should be invoked when the user registers or logs in depending on your use case.

What this method does is to create a new list of player-ids for a user, if it doesn't already exist.
`FieldValue.arrayUnion()` ensures that there are no duplicate player-ids in the array.
```
savePlayerId() async {
//Call the required instances.
    final firebaseAuth = FirebaseAuth.instance;
    final firestore = FirebaseFirestore.instance;
    try {
      final subState = await OneSignal.shared.getPermissionSubscriptionState();
      final deviceId = subState.subscriptionStatus.userId;
      final firestoreDeviceIdReference = await firestore
          .collection("onesignalids")
          .where("customer_id", isEqualTo: firebaseAuth.currentUser?.uid ?? "")
          .get();
      if (firestoreDeviceIdReference != null &&
          firestoreDeviceIdReference.size != 0) {
        await firestore
            .collection("onesignalids")
            .doc(firestoreDeviceIdReference.docs[0].id)
            .update({
          "player_ids": FieldValue.arrayUnion([deviceId])
        });
      } else {
        await firestore.collection("onesignalids").add({
          "customer_id": firebaseAuth.currentUser?.uid ?? "",
          "player_ids": [deviceId],
        });
      }
    } catch (e) {
      print("ERROR $e");
    }
  }
}
```
Next, you create your notifications collection on Firebase FireStore.


1. Create a FireStore collection named 'notifications' where you would save the notifications for all users.

2. Add the following fields to the collection.
   (i) The `customer_id` field which takes the user ID of the recipient of the notification.
   (ii) The `title` field that takes the title of your notification.
   (iii) The `detail` field that takes in the notification payload.
   (iv) The `time` field that takes the timestamp of the notification.

PS: You can name these fields however you want to, as long as you remain consistent.


IMPORTANT: To be able to use cloud functions, you need to upgrade your project's plan to the Blaze plan. You can easily upgrade in the settings section of your project.

To be able to use cloud functions, you must have Node installed. This gives you access to run npm functions.

If you don't already have firebase tools installed,
run `npm install -g firebase-tools` in the command prompt.

Ensure to run `firebase login` in the command prompt to log in on your device.


Let's create and deploy our cloud function!

1. Create a new folder and run `firebase init functions` in cmd.
2. Select your Firebase project and be sure to install dependencies with npm.

Also, run `npm install https` as you will need it to make API calls.

We will make use of Javascript to write our cloud functions.

![CMD IMAGE](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82tv9eovooh8k9d08tb9.png)
![CMD IMAGE](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alyelbly54kq57ve577z.png)

3. Open the index.js in the folder in your code editor (VSCode or any preferred editor) and follow the next steps.

First, you need to load the modules you will require for this project.

```
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config.functions);
 const https = require("https");

```

Next, we create a method that handles sending of push notifications via an API call.

This function takes in our push notification data containing our message and the player IDs of recipients and makes a request to the OneSignal server to have them sent.

```
const sendNotification = (data) => {
  var headers = {
    "Content-Type": "application/json; charset=utf-8",
    Authorization: "Basic $YOUR ONESIGNAL API KEY",
  };

  var options = {
    host: "onesignal.com",
    port: 443,
    path: "/api/v1/notifications",
    method: "POST",
    headers: headers,
  };

  var req = https.request(options, (res) => {
    console.log("statusCode:", res.statusCode);
    console.log("headers:", res.headers);
    res.on("data", (data) => {
      console.log("Response:");
      console.log(JSON.parse(data));
    });
  });


  req.on("error", (e) => {
    console.log("ERROR:");
    console.log(e);
  });

  req.write(JSON.stringify(data));
  req.end();
};
```

Next, we assign a function that is executed when a document is created on our notifications collection in FireStore to an export.

The 'pushNotificationMessage' export function is responsible for fetching the player IDs and notification message, and sending it to the OneSignal server for delivery to the recipients.

```
exports.pushNotificationMessage = functions.firestore.document("notifications/{id}").onCreate(async (snapshot, context) => {
 //OnCreate function will go here. 
});
```

Let's walk through the 'onCreate' callback.

First, we fetch the player ID of the notification's recipient. 

```

  var playerIdsDocs = await admin.firestore().collection("onesignalids")
  .where("customer_id","==" , snapshot.data().customer_id,)
  .get(); //this fetches the document containing the user's player //ids
  var playerIds = [];
  if(!playerIdsDocs.empty){
     playerIdsDocs.forEach((playerIdSingleDoc)=>{
       playerIds = playerIdSingleDoc.data().player_ids; // this //assigns the player id(s) to the list variable above.
     });
}
```
Note : Only one document would be fetched for each user.

Next, we save the notification in a message variable.
```
var message = {
        app_id: "YOUR APP ID",
        contents: { en : snapshot.data().detail },
        headings: { en : snapshot.data().title },
        include_player_ids : playerIds,
      };
```

Finally, for the callback, we call the 'sendNotification' function we defined earlier.

```
   if(!playerIds.empty){
      return sendNotification(message);
   }
      else{
        return ;
      }
```


The only thing left to do is to open the terminal and run `firebase deploy`.

Every time the notifications collection is updated, OneSignal will send a notification on your behalf.

The complete cloud functions code can be viewed below.

{% gist https://gist.github.com/lordlamee/65fc06d372bc73424080421bbfd0544f %}