Flutter Firebase Messaging:如何在指定时间向用户发送推送通知

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

目前我有一个应用程序有一个 Firestore 集合,其中包含特定时间的待办事项,例如 5:00pm - Go for a walk

我创建了一个功能,一旦用户指定了时间,我的 cloud functions 就可以使用 Firebase 云消息传递创建推送通知。

但是,我希望能够在特定时间和日期向用户发送推送通知。

这是我的 cloud functions 文件

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);
    });

}

Firebase Cloud Messaging API 会在您调用后尽快发送消息。它没有办法为未来安排交付。所以你必须自己实施。

部分选项:

  • 您可以使用 scheduled Cloud Function 定期检查是否有任何消息需要传递。
  • 您可以使用 Cloud Tasks 动态安排交付,而不是定期检查。

对于这两个,也可以在这里看到我的回答:

另一种选择:

  • 如果消息本身不是动态的,您可以立即send a data message到设备,然后仅在通知到期时才在设备上显示。

另见:

  • FCM Schedule delivery date or time of push notification

  • How to Send and receive Scheduled notifications FCM Flutter

  • 还有更多 search results

使用上面 Frank 的回答,我能够成功地利用 Cloud Tasks 和 Firebase Cloud Messaging 来实现我的功能!它还有一些问题需要解决,但我想如果需要的话,以后有人可以使用它。

我的代码主要基于 suggested article

这是生成的代码 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)
    }
})