pythonNovice
pythonNovice

Reputation: 1431

Flutter Firebase Messaging: How to send push notifications to users at specified time

Currently I have an app that has a Firestore collection with To Do items at a specific time e.g 5:00pm - Go for a walk

I have created a function that once a user has specified a time, my cloud functions can create a push notification using Firebase Cloud Messaging.

However, I want to be able to send the user that push notification at that specific time & date.

Here is my cloud functions file

async function onCreateNotification(uid, time, text) {
    const user = admin.firestore().collection('users').doc(uid).get();
    // Make this function execute at a specific time
    await admin.messaging().sendToDevice(
        user.tokens,
        {
            data: {
                user: JSON.stringify(user),
                text: JSON.stringify(text)
            }
        },
        {
            // Required for background/quit data-only messages on iOS
            contentAvailable: true,
            // Required for background/quit data-only messages on Android
            priority: "high"
        }
    ).then((response) => {
        // See the MessagingDevicesResponse reference documentation for
        // the contents of response.
        console.log('Successfully sent message:', response);
    }).catch((error) => {
        console.log('Error sending message:', error);
    });

}

Upvotes: 1

Views: 2331

Answers (2)

pythonNovice
pythonNovice

Reputation: 1431

Using Frank's answer above, I was able to successfully utilize Cloud Tasks and Firebase Cloud Messaging to implement my feature! It's got a few kinks left to work out still, but I figured someone could use this later on if need be.

I mostly based my code from the suggested article

Here's the resulting code cloud functions

const functions = require("firebase-functions");
const admin = require('firebase-admin')
const { CloudTasksClient } = require('@google-cloud/tasks');
admin.initializeApp()

exports.createTask = functions.https.onCall(async (data, context) => {
    log("Create Task")
    const taskClient = new CloudTasksClient();
    let { time, uid, id } = data

    // Get Date from time in format mm-dd-yyyy
    let entryDate = new Date(time[0], time[1], time[2], time[3], time[4],);
    const date = join(entryDate, '-');
    let prevEntry = await admin.firestore().doc(`/users/${uid}/${date}/${id}`).get()
    let prevEntryData = await prevEntry.data()

    if (prevEntryData.hasOwnProperty('expirationTask')) {
        // Delete old expiration task
        await taskClient.deleteTask({ name: prevEntryData.expirationTask })
    }
    // This works now! Now I should create a task on google tasks
    const todayDate = new Date()

    const expirationAtSeconds = (entryDate.getTime() - new Date().getTime()) / 1000 

    const project = JSON.parse(process.env.FIREBASE_CONFIG).projectId
    const location = 'us-central1'
    const queue = 'firestore-ttl'
    const queuePath = taskClient.queuePath(project, location, queue)
    const url = `https://${location}-${project}.cloudfunctions.net/firestoreTtlCallback`
    const docPath = `/users/${uid}/${date}/${id}`
    const payload = {
        docPath,
        uid,
    }
    const task = {
        httpRequest: {
            httpMethod: 'POST',
            url,
            body: Buffer.from(JSON.stringify(payload)).toString('base64'),
            headers: {
                'Content-Type': 'application/json'
            },
        },
        scheduleTime: {
            seconds: expirationAtSeconds
        }
    }
    const [response] = await taskClient.createTask({ parent: queuePath, task })
    const expirationTask = response.name;
    const update = { expirationTask }
    // update the entry with the expiration task name
    await admin.firestore().doc(docPath).update(update)
    log("Done with Create Task")
    return ['Success!']
})

// Callback to send message to users
exports.firestoreTtlCallback = functions.https.onRequest(async (req, res) => {
    try {
        const payload = req.body;
        let entry = await (await admin.firestore().doc(payload.docPath).get()).data();
        let tokens = await (await admin.firestore().doc(`/users/${payload.uid}`).get()).get('tokens')
        // log(entry);
        // log(tokens)
        await admin.messaging().sendToDevice(
            tokens,
            {
                data: {
                    title: JSON.stringify('App'),
                    body: JSON.stringify(entry['text'])
                }
            },
            {
                contentAvailable: true,
                priority: 'high'
            }
        ).then((response) => {
            log('Successfully sent message:')
            log(response)
            admin.firestore().doc(payload.docPath).update({ expirationTask: admin.firestore.FieldValue.delete() })
        }).catch((error) => {
            log('Error in sending Message')
            log(error)
        })
        res.status(200)
    } catch (err) {
        log(err)
        res.status(500).send(err)
    }
})

Upvotes: 0

Frank van Puffelen
Frank van Puffelen

Reputation: 600006

The Firebase Cloud Messaging API delivers the messages as soon as possible after you call it. It doesn't have a way to schedule delivery for the future. So you will have to implement that yourself.

Some options:

  • You can use a scheduled Cloud Function that periodically checks if there are any messages that need to be delivered.
  • You can use Cloud Tasks to dynamically schedule the delivery, instead of periodically checking.

For both of those, also see my answer here: Is there any TTL (Time To Live ) for Documents in Firebase Firestore

As another alternative:

  • If the message itself is not dynamic, you can send a data message to the device right away, and then only display it on the device once the notification is due.

Also see:

Upvotes: 1

Related Questions