How to add push notifications to your Appwrite Cloud apps

Femi-ige Muyiwa - Dec 7 '23 - - Dev Community

In today's information-driven landscape, staying current is paramount. Enter the realm of push notifications — an avenue by which data arrives at our digital doorstep, like a knowledgeable neighbor extending a warm invitation. These little alerts have become one of the most essential features in modern-day applications as they update the user about the services that a given application handles.

Let’s explore push notifications a bit further: messages or alerts sent from a server or backend system to a user's mobile device, computer, or other device. These notifications are "pushed" to the device rather than being requested or "pulled" by the user.

In the case of our analogy above, the friendly neighbor is Appwrite. Appwrite is an open-source backend-as-a-service (BaaS) that simplifies repetitive development tasks through simple REST API. Appwrite has added this important feature to its services. In this article, we’ll take a closer look at how we can add push notifications to our Flutter applications using Appwrite's cloud functions.

Before we can proceed, you’ll need to have the following:

  • An Appwrite cloud account
  • A Firebase account (subsequently, a Gmail account)
  • A good foundation in Flutter
  • And some coffee for the cookies, of course. 😉

Getting started

Appwrite Cloud implements push notifications in its application by combining the Firebase Cloud Messaging (FCM) feature provided by Firebase and its own Appwrite Cloud Function API. Appwrite further simplifies this process programmatically by providing a serverless Node.js code template that allows us to connect our Flutter application to communicate with Firebase Cloud Messaging.

We’re going to get pretty granular in this tutorial, so here’s a helpful outline:

Creating projects

Thus, without further ado, let’s create the Firebase project!

Excited

Creating a Firebase project

To create a Firebase project, click the earlier link and click Create a project on the page.

Create project

Then, Firebase prompts us to fill in a project name, turn on or off analytics (uncheck analytics as it is optional for this project), and finally, continue. That’s it! You have now successfully created a Firebase project.

Add project name

Analytics

Continue

Creating an Appwrite project

After creating an Appwrite cloud account, log into the console, click Create Project, and fill in a project name and ID here.

Note: ID can also be autogenerated.

Create an Appwrite project

Creating a Flutter project

To create a Flutter project, open your terminal and type the command below:

flutter create <project name>
Enter fullscreen mode Exit fullscreen mode

Note: Change the to a name of your choice.

The command above triggers the Flutter SDK to create a new boilerplate project; thus, we have successfully created a Firebase, Appwrite, and Flutter project.

Setting up Firebase Cloud Messaging

To use the Firebase cloud messaging feature within our Appwrite cloud applications, we will need to get the private key and database URL from the Firebase console.

Generating a private key

Starting with the private key, we will navigate to the console's Project settings area by clicking the gear icon.

Project settings

Next, we will copy the project ID to a safe place, as we will require it when creating the FCM template in Appwrite. We must provide the project ID as a variable, which we will then pass to the FCM template as a parameter.

Project ID

Next, we'll move to the Service accounts section of the Project Settings area and click Generate new private key, then Generate key. Doing this will download a JSON file containing important information to our PC.

Generate private key

Generate key

Creating a Realtime database

There are two options for databases to select from within Firebase; for this project, either is effective. For this project, we will choose the Realtime database as our choice. Thus, enlarge the build section within the Firebase console, select Realtime database, and click Create database.

Realtime database build

Create database

This will prompt a pop-up asking us to set up the database. Select the United States (us-central1) as your preferred database location. Select Start in Test mode for the security rules, as we are not using the database for any production purpose. Then click Enable to create the Realtime database.

Set up a database location

Set up database security rules

Now, the database URL is essential for what we want to build — so let’s copy the URL to a safe place and move to the next item on the list.

Realtime database URL

Setting up Appwrite

Now that we have successfully gotten the private key and database URL, the next step is to create an Appwrite database and cloud function and customize the FCM cloud function template.

Creating an Appwrite database

After creating an Appwrite project, navigate to the database section within the Appwrite console and click Create database. Next, we will create a collection with a single String attribute called Token. This attribute will be the placeholder for the device token generated from the device we intend to receive the push notification.

create databases

Create collections
Create attributes

After, move to the collection's settings and add permissions by setting the role to any and selecting all the CRUD (create, read, update, and delete) permissions. Setting permissions allows anyone with the database ID and collection ID (in this case, the admin) to access the notification device token.

Roles
Permissions

Creating an Appwrite Cloud function FCM template

Appwrite's application of simple yet effective designs makes navigation relatively smooth — this is evident in the creation of Functions, as all that is required is to select Functions in the console from the navigation bar. To create the FCM template, select the Create function button within the function area.

Functions

Then, a pop-up appears, showing all kinds of functions available to the Appwrite cloud. We will proceed to select All templates in the bottom right corner.

All templates

In the All templates section, we can filter our template search by use case or runtime or search it by a named keyword. Using the use case filter, we will select the messaging option, select push notification with FCM among the options given, and click Create function.

fcm_functions

Appwrite further prompts us to sort out the configuration of the function by setting a name (any name of our choosing) and runtime (Node.js 18.0).

Configurations

The Variables section is where the primary business lies, where we must add some information in the private key JSON file and the database URL copied from the Firebase project earlier. So, from the private key JSON file, we will copy out the project ID (alternatively, we can get the project ID from the Firebase project settings), private key, and client_email. Also, we will add the URL from the Realtime database — then, we are good to go.

Variables

Private key JSON

In the Connect section, we can add the template to an available repository or create a new one. For the tutorial's sake, we will select Create a new repository, then click next to set up the Git organization and the repository name.

Connect
Repository

Finally, we can specify our preferred Git branch intended for the template (it is main by default). After completing all the steps, we can create a push notification FCM template.

Branch

Customzing the FCM template code

To customize the FCM template code, head to the repository containing the template code on GitHub, and from the code section, navigate to the src folder. The src folder contains two files: main.js and utils.js.

Update the main.js file with the code below:

import { throwIfMissing, sendPushNotification } from './utils.js';

export default async ({ req, res, log, error }) => {
    throwIfMissing(process.env, [
        'FCM_PROJECT_ID',
        'FCM_PRIVATE_KEY',
        'FCM_CLIENT_EMAIL',
        'FCM_DATABASE_URL',
    ]);

    try {
        throwIfMissing(req.body, ['deviceToken', 'message']);
        throwIfMissing(req.body.message, ['title', 'body']);
    } catch (err) {
        return res.json({ ok: false, error: err.message }, 400);
    }

    log(`Sending message to device: ${process.env.FCM_PROJECT_ID}`);

    try {
        const response = await sendPushNotification({
            notification: {
                title: req.body.message.title,
                body: req.body.message.body,
            },
            token: req.body.deviceToken,
        });
        log(`Successfully sent message: ${response}`);

        return res.json({ ok: true, messageId: response });
    } catch (e) {
        error(e);
        return res.json({ ok: false, error: `Failed to send the message ${e}` }, 500);
    }
};

Enter fullscreen mode Exit fullscreen mode

Also, we will update the utils.js file with the code below:

import admin from 'firebase-admin';

/**
 * Throws an error if any of the keys are missing from the object
 * @param {*} obj
 * @param {string[]} keys
 * @throws {Error}
 */
export function throwIfMissing(obj, keys) {
    const missing = [];
    for (let key of keys) {
        if (!(key in obj) || !obj[key]) {
            missing.push(key);
        }
    }
    if (missing.length > 0) {
        throw new Error(`Missing required fields: ${missing.join(', ')}`);
    }
}

/**
 * @param {admin.messaging.Message} payload
 * @returns {Promise<string>}
 */

export async function sendPushNotification(payload) {
    if (!admin.apps.length) {
        admin.initializeApp({
            credential: admin.credential.cert({
                projectId: process.env.FCM_PROJECT_ID,
                clientEmail: process.env.FCM_CLIENT_EMAIL,
                privateKey: process.env.FCM_PRIVATE_KEY.replace(/\\n/gm, "\n"),
            }),
            databaseURL: process.env.FCM_DATABASE_URL,
        });
    }
    return await admin.messaging().send(payload);
}
Enter fullscreen mode Exit fullscreen mode

After doing that, let’s commit our changes. Due to Appwrite’s connection to our GitHub, the commit automatically triggers the cloud Function to redeploy as it monitors changes in the attached repository.

Now, we’re ready to clone the Flutter applications — both the admin (trigger application) and push notification application (receiver application).

Joy

Building the Flutter project

Earlier in the article, we indicated that we would build two Flutter applications during the project. One is the device receiving the push notification; the other will be an admin device that triggers the Appwrite cloud FCM template. Doing this gives us a better use case for push notifications programmatically. Let’s get into it.

Cloning the application for the notification device

In this section, we will clone the application for the notification device from this repository. The application’s UI contains a button that uploads the phone token obtained from an FCM request.

After cloning the application to local storage, open it in your preferred code editor and run the command below:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

This command obtains all the dependencies listed in the pubspec.yaml file in the current working directory and their transitive dependencies. Next, run the command flutter run, and the application should look like the video below:

To send a push notification, a token from the recipient device must be available to the Firebase cloud messaging service. The question now is, how can we achieve that?

The first step involves adding our Flutter project to the Firebase project created earlier. So, starting with that, we can achieve that manually through the Firebase console or using the Firebase CLI (command line interface).

The Firebase CLI is a simple tool that allows easy interactions with Firebase using the command line. The Firebase CLI provides commands that let us send specific requests to Firebase and get adequate responses. Some of these requests include the following:

  • Project Setup
  • Authentication and Authorization
  • Real-time Database
  • Hosting
  • Cloud Functions
  • Cloud Storage
  • Firestore
  • Test Lab
  • Analytics

Thus, to use the Firebase CLI for project setup within a Flutter project, let’s head back to the Firebase console. Within the base console are two options for connecting an Android application to Firebase. So, we will select the second option that says Flutter.

flutter project

When we click the option, the first step asks that we install the Firebase CLI. According to the link, there are several methods we can follow to install the Firebase CLI, depending on our device’s operating system. We will use the node package manager (npm) to install the CLI for uniformity. Here is a guide on how to install Node.js on your device.

After installing Node.js, run the command below within your terminal:

npm install -g firebase-tools
Enter fullscreen mode Exit fullscreen mode

firebase cli

Doing this automatically installs the Firebase CLI to our preferred device, and with that, we can proceed to the next step of logging into our Firebase console via the CLI. To do that, we will use the command:

firebase login
Enter fullscreen mode Exit fullscreen mode

After logging into the Firebase CLI, we must install the FlutterFire plugin to create a new Firebase project in Dart. FlutterFire is a set of plugins that connects our Flutter project to Firebase. To activate the FlutterFire plugin on our PC, use the following command:

dart pub global activate flutterfire_cli
Enter fullscreen mode Exit fullscreen mode

To use the FlutterFire plugin to configure our Firebase project within the Flutter app, we must open a terminal within the Flutter project folder and then run this command:

flutterfire configure --project=<Firebase Project ID>
Enter fullscreen mode Exit fullscreen mode

Note: Remember to change the <Firebase Project ID> in the command above to the ID of the Firebase project.

flutter_fire

flutter_fire_2

After selecting what platform(s) the configuration should support, the CLI will add some essential files to the Flutter project, and we are good to proceed.

Next, we will connect our Flutter application to Appwrite. We do this to upload the generated token from our notification device to a general database — this allows a central admin to access the token and send push notifications accordingly to several devices.

Here is how to connect an IOS or Android device in Flutter to Appwrite:

iOS

First, obtain the bundle ID by navigating to the project.pbxproj file (ios > Runner.xcodeproj > project.pbxproj) and searching for the PRODUCT_BUNDLE_IDENTIFIER.

Next, navigate to the Runner.xcworkspace folder in the application’s iOS folder in the project directory on Xcode. To select the runner target, choose the Runner project in the Xcode project navigator and find the Runner target. Next, select General and IOS 11.0 as the target in the deployment info section.

iOS

Android

For Android, copy the XML script below and paste it below the activity tag in the Androidmanifest.xml file (to find this file, head to android > app > src > main).

Note: change [PROJECT-ID] to the ID you used when creating the Appwrite project.

We will also need to set up a platform within the Appwrite console. Follow the steps below to do so.

  • Within the Appwrite console, select Create Platform and choose Flutter for the platform type.
  • Specify the operating system: in this case, Android.
  • Finally, provide the application and package names (found in the app-level build.gradle file).

Create platform 1

Create platform 2

Cloning the application for the admin device

In this section, we will clone the application for the admin device. The application’s UI contains a button that triggers the cloud function. Let’s clone the repository specified in the prerequisites.

After cloning the application to local storage, we will open it in our preferred code editor and run the command below:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

This command obtains all the dependencies listed in the pubspec.yaml file in the current working directory and their transitive dependencies. Next, run the command flutter run, and our application should look like the images below:

admin

send_push_notification

So, when we click the Send Push Notification button, it triggers this function in the app_controller.dart file:

void sendNotification(String token) async {
    try {
      final Map < String, dynamic > requestBody = {
            'deviceToken': token,
                'message': {
                'title': 'Notification Title',
                    'body': 'Notification Body',
        },
        };
      final result = await functions.createExecution(
            functionId: Appconstants.functionId,
            body: jsonEncode(requestBody),
            path: '/',
            method: 'POST',
            headers: {
            'Content-Type': 'application/json',
        },
        );
    } catch (e) {
        print(e);
    }
}
Enter fullscreen mode Exit fullscreen mode

This function encodes a Map<String, dynamic> into a JSON and programmatically sends a POST request to the Appwrite Function. Without an admin page, we can trigger these notifications within the Appwrite console.

To do this, navigate to the Function section of the console, and within the FCM cloud function template, select Execute now.

function_fcm
execute_fcm

We will leave the Path as it is in the pop-up, but we‘ll change the method to POST. In the Advanced settings section within the pop-up, add a Header with its name being Content-Type and value being application/json. Finally, in the body section, we will add a JSON that looks like the format below:

{
    "deviceToken": "<notification device token>",
        "message": {
        "title": "hi",
            "body": "how are you"
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we will execute the function, and the result should look like the GIF below:

Conclusion

Push notifications are hugely important in modern applications. By incorporating push notifications into our Appwrite Cloud applications, we can keep our users informed and engaged. Leveraging this powerful communication channel ensures that our audience receives timely updates, personalized messages, and essential information — enhancing their overall experience.

With our backend configuration and error handling in place, we can reliably deliver push notifications while minimizing potential issues. Integrating push notifications into our applications is a valuable strategy for boosting user engagement and ensuring effective communication with our user base.

Resources

Here are some additional resources that you might find useful.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .