From fca8b504bd2f2309e6f940c8070723f147c26d38 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Mon, 30 Jan 2023 16:30:25 +0800 Subject: [PATCH 01/13] sample implementation of registration tokens for FCM --- messaging/app/build.gradle | 1 + messaging/app/src/main/AndroidManifest.xml | 11 --- .../quickstart/fcm/kotlin/MainActivity.kt | 75 +++++++++++++++---- .../fcm/kotlin/MyFirebaseMessagingService.kt | 16 +++- messaging/functions/index.js | 9 +++ 5 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 messaging/functions/index.js diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index f6f6f8270..aad81a3fd 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -71,6 +71,7 @@ dependencies { implementation 'com.google.firebase:firebase-installations-ktx:17.1.0' implementation 'androidx.work:work-runtime:2.7.1' + implementation 'com.google.firebase:firebase-firestore-ktx:24.1.0' // Testing dependencies androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/messaging/app/src/main/AndroidManifest.xml b/messaging/app/src/main/AndroidManifest.xml index 7ccf60a01..665a8dd50 100644 --- a/messaging/app/src/main/AndroidManifest.xml +++ b/messaging/app/src/main/AndroidManifest.xml @@ -36,8 +36,6 @@ - - @@ -46,15 +44,6 @@ - - - - - - - diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 2db8171f5..9fe02d1cc 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -3,6 +3,7 @@ package com.google.firebase.quickstart.fcm.kotlin import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.os.Bundle @@ -11,11 +12,18 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat -import com.google.android.gms.tasks.OnCompleteListener +import com.google.firebase.Timestamp +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.ktx.firestore import com.google.firebase.ktx.Firebase import com.google.firebase.messaging.ktx.messaging import com.google.firebase.quickstart.fcm.R import com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.await +import java.util.* class MainActivity : AppCompatActivity() { @@ -80,25 +88,46 @@ class MainActivity : AppCompatActivity() { binding.logTokenButton.setOnClickListener { // Get token // [START log_reg_token] - Firebase.messaging.getToken().addOnCompleteListener(OnCompleteListener { task -> - if (!task.isSuccessful) { - Log.w(TAG, "Fetching FCM registration token failed", task.exception) - return@OnCompleteListener - } - - // Get new FCM registration token - val token = task.result - + GlobalScope.launch { + val token = getAndStoreRegToken() // Log and toast - val msg = getString(R.string.msg_token_fmt, token) - Log.d(TAG, msg) - Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() - }) + Log.d(TAG, "on click listener: $token") + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken) + } // [END log_reg_token] } Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() askNotificationPermission() + + // In the app’s first Activity + val preferences = this.getPreferences(Context.MODE_PRIVATE) + val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) + Log.d(TAG, "last refresh in preferences: $lastRefreshLong") + GlobalScope.launch { + val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() + val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 + Log.d(TAG, "updatedTime: $updatedTime from Firestore") + val lastRefreshDate = Date(if (lastRefreshLong == -1L) updatedTime else lastRefreshLong) + val c = Calendar.getInstance() + c.time = lastRefreshDate + c.add(Calendar.DATE, 30) + val today = Date() + if (today.after(c.time)) { + // get token and store into Firestore (see function above) + getAndStoreRegToken() + // update device cache + Log.d(TAG, "get registration token and store") + preferences.edit().putLong("lastRefreshDate", updatedTime) + } + } } private fun askNotificationPermission() { @@ -115,6 +144,24 @@ class MainActivity : AppCompatActivity() { } } + private suspend fun getAndStoreRegToken(): String { + var token = "" + runBlocking { + token = Firebase.messaging.token.await() + Log.d(TAG, "getRegistrationToken(): $token") + // Add token and timestamp to Firestore for this user + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken) + } + return token + } + companion object { private const val TAG = "MainActivity" diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index 4828253c2..78fe90aa8 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -11,6 +11,9 @@ import android.util.Log import androidx.core.app.NotificationCompat import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.ktx.Firebase import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.google.firebase.quickstart.fcm.R @@ -74,7 +77,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { // If you want to send messages to this application instance or // manage this apps subscriptions on the server side, send the // FCM registration token to your app server. - sendRegistrationToServer(token) + storeToken(token) } // [END on_new_token] @@ -103,9 +106,18 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { * * @param token The new token. */ - private fun sendRegistrationToServer(token: String?) { + private fun storeToken(token: String?) { // TODO: Implement this method to send token to your app server. Log.d(TAG, "sendRegistrationTokenToServer($token)") + // Add token and timestamp to Firestore for this user + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken) } /** diff --git a/messaging/functions/index.js b/messaging/functions/index.js new file mode 100644 index 000000000..7d2549d59 --- /dev/null +++ b/messaging/functions/index.js @@ -0,0 +1,9 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); + +admin.initializeApp(); +exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((context) => { + admin.firestore().doc('refresh/refreshDate').set({ lastRefreshDate : Date.now() }); +}); From 32c4b65387ffdcd28ed1c6aafa72eed081e7cf96 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Tue, 31 Jan 2023 11:11:18 +0800 Subject: [PATCH 02/13] made changes according to comments --- messaging/app/build.gradle | 7 ++++++- messaging/app/src/main/AndroidManifest.xml | 11 +++++++++++ .../quickstart/fcm/kotlin/MainActivity.kt | 17 ++++++++++------- .../fcm/kotlin/MyFirebaseMessagingService.kt | 4 ++-- messaging/gradle.properties | 1 + 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index aad81a3fd..c3723d81e 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -70,9 +70,14 @@ dependencies { implementation 'com.google.firebase:firebase-installations-ktx:17.1.0' - implementation 'androidx.work:work-runtime:2.7.1' + // Used to store FCM registration tokens implementation 'com.google.firebase:firebase-firestore-ktx:24.1.0' + implementation 'androidx.work:work-runtime:2.7.1' + + // Used for Firestore + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + // Testing dependencies androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test:runner:1.5.2' diff --git a/messaging/app/src/main/AndroidManifest.xml b/messaging/app/src/main/AndroidManifest.xml index 665a8dd50..7ccf60a01 100644 --- a/messaging/app/src/main/AndroidManifest.xml +++ b/messaging/app/src/main/AndroidManifest.xml @@ -36,6 +36,8 @@ + + @@ -44,6 +46,15 @@ + + + + + + + diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 9fe02d1cc..20eb44fa3 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -12,6 +12,7 @@ import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.google.firebase.Timestamp import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.ktx.firestore @@ -23,7 +24,8 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await -import java.util.* +import java.util.Calendar +import java.util.Date class MainActivity : AppCompatActivity() { @@ -88,7 +90,7 @@ class MainActivity : AppCompatActivity() { binding.logTokenButton.setOnClickListener { // Get token // [START log_reg_token] - GlobalScope.launch { + lifecycleScope.launch { val token = getAndStoreRegToken() // Log and toast Log.d(TAG, "on click listener: $token") @@ -99,7 +101,7 @@ class MainActivity : AppCompatActivity() { // Get user ID from Firebase Auth or your own server Firebase.firestore.collection("fcmTokens").document("myuserid") - .set(deviceToken) + .set(deviceToken).await() } // [END log_reg_token] } @@ -116,9 +118,10 @@ class MainActivity : AppCompatActivity() { val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 Log.d(TAG, "updatedTime: $updatedTime from Firestore") val lastRefreshDate = Date(if (lastRefreshLong == -1L) updatedTime else lastRefreshLong) - val c = Calendar.getInstance() - c.time = lastRefreshDate - c.add(Calendar.DATE, 30) + val c = Calendar.getInstance().apply { + time = lastRefreshDate + add(Calendar.DATE, 30) + } val today = Date() if (today.after(c.time)) { // get token and store into Firestore (see function above) @@ -144,7 +147,7 @@ class MainActivity : AppCompatActivity() { } } - private suspend fun getAndStoreRegToken(): String { + private fun getAndStoreRegToken(): String { var token = "" runBlocking { token = Firebase.messaging.token.await() diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index 78fe90aa8..826a6848f 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -77,7 +77,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { // If you want to send messages to this application instance or // manage this apps subscriptions on the server side, send the // FCM registration token to your app server. - storeToken(token) + sendTokenToServer(token) } // [END on_new_token] @@ -106,7 +106,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { * * @param token The new token. */ - private fun storeToken(token: String?) { + private fun sendTokenToServer(token: String?) { // TODO: Implement this method to send token to your app server. Log.d(TAG, "sendRegistrationTokenToServer($token)") // Add token and timestamp to Firestore for this user diff --git a/messaging/gradle.properties b/messaging/gradle.properties index aac7c9b46..e78e65c08 100644 --- a/messaging/gradle.properties +++ b/messaging/gradle.properties @@ -15,3 +15,4 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +android.useAndroidX=true From 4ef6bbaaf9a3ae22d3cbae4ab346960e92d47cca Mon Sep 17 00:00:00 2001 From: andreaowu Date: Tue, 31 Jan 2023 21:00:44 +0800 Subject: [PATCH 03/13] resolve comments and remove logs --- .../quickstart/fcm/kotlin/MainActivity.kt | 31 +++++++------------ .../fcm/kotlin/MyFirebaseMessagingService.kt | 1 - 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 20eb44fa3..8c3915013 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -22,7 +22,6 @@ import com.google.firebase.quickstart.fcm.R import com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await import java.util.Calendar import java.util.Date @@ -93,7 +92,6 @@ class MainActivity : AppCompatActivity() { lifecycleScope.launch { val token = getAndStoreRegToken() // Log and toast - Log.d(TAG, "on click listener: $token") val deviceToken = hashMapOf( "token" to token, "timestamp" to FieldValue.serverTimestamp(), @@ -112,11 +110,9 @@ class MainActivity : AppCompatActivity() { // In the app’s first Activity val preferences = this.getPreferences(Context.MODE_PRIVATE) val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) - Log.d(TAG, "last refresh in preferences: $lastRefreshLong") GlobalScope.launch { val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 - Log.d(TAG, "updatedTime: $updatedTime from Firestore") val lastRefreshDate = Date(if (lastRefreshLong == -1L) updatedTime else lastRefreshLong) val c = Calendar.getInstance().apply { time = lastRefreshDate @@ -127,7 +123,6 @@ class MainActivity : AppCompatActivity() { // get token and store into Firestore (see function above) getAndStoreRegToken() // update device cache - Log.d(TAG, "get registration token and store") preferences.edit().putLong("lastRefreshDate", updatedTime) } } @@ -147,21 +142,17 @@ class MainActivity : AppCompatActivity() { } } - private fun getAndStoreRegToken(): String { - var token = "" - runBlocking { - token = Firebase.messaging.token.await() - Log.d(TAG, "getRegistrationToken(): $token") - // Add token and timestamp to Firestore for this user - val deviceToken = hashMapOf( - "token" to token, - "timestamp" to FieldValue.serverTimestamp(), - ) - - // Get user ID from Firebase Auth or your own server - Firebase.firestore.collection("fcmTokens").document("myuserid") - .set(deviceToken) - } + private suspend fun getAndStoreRegToken(): String { + var token = Firebase.messaging.token.await() + // Add token and timestamp to Firestore for this user + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken).await() return token } diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index 826a6848f..a0642bd28 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -108,7 +108,6 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { */ private fun sendTokenToServer(token: String?) { // TODO: Implement this method to send token to your app server. - Log.d(TAG, "sendRegistrationTokenToServer($token)") // Add token and timestamp to Firestore for this user val deviceToken = hashMapOf( "token" to token, From 61a17675ed559f86348206239193e673bbce57a1 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Thu, 2 Feb 2023 15:42:56 +0800 Subject: [PATCH 04/13] fix scope --- .../com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 8c3915013..26a82f996 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -20,7 +20,6 @@ import com.google.firebase.ktx.Firebase import com.google.firebase.messaging.ktx.messaging import com.google.firebase.quickstart.fcm.R import com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import java.util.Calendar @@ -110,7 +109,7 @@ class MainActivity : AppCompatActivity() { // In the app’s first Activity val preferences = this.getPreferences(Context.MODE_PRIVATE) val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) - GlobalScope.launch { + lifecycleScope.launch { val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 val lastRefreshDate = Date(if (lastRefreshLong == -1L) updatedTime else lastRefreshLong) From 5692eb545a17ed4e3f3fefe4813d09331af23d92 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Thu, 2 Feb 2023 16:29:00 +0800 Subject: [PATCH 05/13] remove duped code and simplify --- .../quickstart/fcm/kotlin/MainActivity.kt | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 26a82f996..bc4e2c746 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -89,16 +89,13 @@ class MainActivity : AppCompatActivity() { // Get token // [START log_reg_token] lifecycleScope.launch { + // Get new FCM registration token val token = getAndStoreRegToken() + // Log and toast - val deviceToken = hashMapOf( - "token" to token, - "timestamp" to FieldValue.serverTimestamp(), - ) - - // Get user ID from Firebase Auth or your own server - Firebase.firestore.collection("fcmTokens").document("myuserid") - .set(deviceToken).await() + val msg = getString(R.string.msg_token_fmt, token) + Log.d(TAG, msg) + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() } // [END log_reg_token] } @@ -110,18 +107,18 @@ class MainActivity : AppCompatActivity() { val preferences = this.getPreferences(Context.MODE_PRIVATE) val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) lifecycleScope.launch { - val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() - val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 - val lastRefreshDate = Date(if (lastRefreshLong == -1L) updatedTime else lastRefreshLong) val c = Calendar.getInstance().apply { - time = lastRefreshDate + time = if (lastRefreshLong == -1L) Date() else Date(lastRefreshLong) add(Calendar.DATE, 30) } + val today = Date() - if (today.after(c.time)) { - // get token and store into Firestore (see function above) + if (today.after(c.time) || lastRefreshLong == -1L) { + // get token and store into Firestore getAndStoreRegToken() - // update device cache + // sync device cache time with Firestore just in case + val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() + val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 preferences.edit().putLong("lastRefreshDate", updatedTime) } } From 1ff3190ea84aabc4dfee74e5259e208b688aee47 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Thu, 2 Feb 2023 16:48:01 +0800 Subject: [PATCH 06/13] added code to remove expired tokens from topics --- messaging/functions/index.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/messaging/functions/index.js b/messaging/functions/index.js index 7d2549d59..05d36cef3 100644 --- a/messaging/functions/index.js +++ b/messaging/functions/index.js @@ -4,6 +4,36 @@ const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); + +/** + * Scheduled function that runs once a month. It updates the last refresh date for + * tokens so that a client can refresh the token if the last time it did so was + * before the refresh date. + */ + exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((context) => { admin.firestore().doc('refresh/refreshDate').set({ lastRefreshDate : Date.now() }); }); + +/** + * Scheduled function that runs once a day. It retrieves all stale tokens then + * unsubscribes them from 'topic1' then deletes them. + * + * Note: topic1 is an example topic here. It is up to the developer to unsubscribe + * all topics the token is subscribed to. + */ +exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { + const staleTokensResult = await admin.firestore().collection('tokens') + .where("timestamp", "<", Date.now() - EXPIRATION_TIME) + .get(); + + const staleTokens = staleTokensResult.docs.map(staleTokenDoc => staleTokenDoc.id); + + await admin.messaging().unsubscribeFromTopic(staleTokens, 'topic1'); + + const deletePromises = []; + for (const staleTokenDoc of staleTokensResult.docs) { + deletePromises.push(staleTokenDoc.ref.delete()); + } + await Promise.all(deletePromises); +}); From cf9f113af9e7f732dc17f4b4723f99cf9dee283e Mon Sep 17 00:00:00 2001 From: andreaowu Date: Thu, 2 Feb 2023 18:39:37 +0800 Subject: [PATCH 07/13] fix functions code --- messaging/functions/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/messaging/functions/index.js b/messaging/functions/index.js index 05d36cef3..5062704a6 100644 --- a/messaging/functions/index.js +++ b/messaging/functions/index.js @@ -5,6 +5,8 @@ const admin = require('firebase-admin'); admin.initializeApp(); +const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days + /** * Scheduled function that runs once a month. It updates the last refresh date for * tokens so that a client can refresh the token if the last time it did so was @@ -23,7 +25,7 @@ exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((contex * all topics the token is subscribed to. */ exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { - const staleTokensResult = await admin.firestore().collection('tokens') + const staleTokensResult = await admin.firestore().collection('fcmTokens') .where("timestamp", "<", Date.now() - EXPIRATION_TIME) .get(); From 7f876a46bf6bc8195ea3386e3444420618037237 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Mon, 6 Feb 2023 13:58:12 +0700 Subject: [PATCH 08/13] small code cleanup --- .../com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index bc4e2c746..5994883ae 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -107,12 +107,12 @@ class MainActivity : AppCompatActivity() { val preferences = this.getPreferences(Context.MODE_PRIVATE) val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) lifecycleScope.launch { + val today = Date() val c = Calendar.getInstance().apply { - time = if (lastRefreshLong == -1L) Date() else Date(lastRefreshLong) + time = if (lastRefreshLong == -1L) today else Date(lastRefreshLong) add(Calendar.DATE, 30) } - val today = Date() if (today.after(c.time) || lastRefreshLong == -1L) { // get token and store into Firestore getAndStoreRegToken() From baa813214d8353eef12e67810aae6b2d851aa3e3 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Tue, 7 Feb 2023 13:21:02 +0700 Subject: [PATCH 09/13] add snippets --- .../firebase/quickstart/fcm/kotlin/MainActivity.kt | 4 ++-- messaging/functions/index.js | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 5994883ae..56875b39b 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -87,7 +87,6 @@ class MainActivity : AppCompatActivity() { binding.logTokenButton.setOnClickListener { // Get token - // [START log_reg_token] lifecycleScope.launch { // Get new FCM registration token val token = getAndStoreRegToken() @@ -97,7 +96,6 @@ class MainActivity : AppCompatActivity() { Log.d(TAG, msg) Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() } - // [END log_reg_token] } Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() @@ -139,6 +137,7 @@ class MainActivity : AppCompatActivity() { } private suspend fun getAndStoreRegToken(): String { + // [START log_reg_token] var token = Firebase.messaging.token.await() // Add token and timestamp to Firestore for this user val deviceToken = hashMapOf( @@ -149,6 +148,7 @@ class MainActivity : AppCompatActivity() { // Get user ID from Firebase Auth or your own server Firebase.firestore.collection("fcmTokens").document("myuserid") .set(deviceToken).await() + // [END log_reg_token] return token } diff --git a/messaging/functions/index.js b/messaging/functions/index.js index 5062704a6..535eed8f2 100644 --- a/messaging/functions/index.js +++ b/messaging/functions/index.js @@ -12,18 +12,20 @@ const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days * tokens so that a client can refresh the token if the last time it did so was * before the refresh date. */ - +// [START refresh_date_scheduled_function] exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((context) => { admin.firestore().doc('refresh/refreshDate').set({ lastRefreshDate : Date.now() }); }); +// [END refresh_date_scheduled_function] /** * Scheduled function that runs once a day. It retrieves all stale tokens then * unsubscribes them from 'topic1' then deletes them. * - * Note: topic1 is an example topic here. It is up to the developer to unsubscribe + * Note: weather is an example topic here. It is up to the developer to unsubscribe * all topics the token is subscribed to. */ +// [START remove_stale_tokens] exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { const staleTokensResult = await admin.firestore().collection('fcmTokens') .where("timestamp", "<", Date.now() - EXPIRATION_TIME) @@ -31,7 +33,7 @@ exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (c const staleTokens = staleTokensResult.docs.map(staleTokenDoc => staleTokenDoc.id); - await admin.messaging().unsubscribeFromTopic(staleTokens, 'topic1'); + await admin.messaging().unsubscribeFromTopic(staleTokens, 'weather'); const deletePromises = []; for (const staleTokenDoc of staleTokensResult.docs) { @@ -39,3 +41,4 @@ exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (c } await Promise.all(deletePromises); }); +// [END remove_stale_tokens] From defd24c0fe0f4975911c731b4b8f940c4f2b76c1 Mon Sep 17 00:00:00 2001 From: andreaowu Date: Wed, 15 Feb 2023 12:58:32 +0700 Subject: [PATCH 10/13] have both optimized and non-optimized versions --- .../quickstart/fcm/kotlin/MainActivity.kt | 83 ++++++++++++++----- .../fcm/kotlin/MyFirebaseMessagingService.kt | 7 ++ 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index 56875b39b..dc047f5f6 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -90,7 +90,6 @@ class MainActivity : AppCompatActivity() { lifecycleScope.launch { // Get new FCM registration token val token = getAndStoreRegToken() - // Log and toast val msg = getString(R.string.msg_token_fmt, token) Log.d(TAG, msg) @@ -101,25 +100,9 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() askNotificationPermission() - // In the app’s first Activity - val preferences = this.getPreferences(Context.MODE_PRIVATE) - val lastRefreshLong = preferences.getLong("lastRefreshDate", -1) - lifecycleScope.launch { - val today = Date() - val c = Calendar.getInstance().apply { - time = if (lastRefreshLong == -1L) today else Date(lastRefreshLong) - add(Calendar.DATE, 30) - } - if (today.after(c.time) || lastRefreshLong == -1L) { - // get token and store into Firestore - getAndStoreRegToken() - // sync device cache time with Firestore just in case - val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() - val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 - preferences.edit().putLong("lastRefreshDate", updatedTime) - } - } + dateRefresh() + optimizedDateRefresh() // optimized version of dateRefresh() that requires using Android SharedPreferences } private fun askNotificationPermission() { @@ -149,11 +132,71 @@ class MainActivity : AppCompatActivity() { Firebase.firestore.collection("fcmTokens").document("myuserid") .set(deviceToken).await() // [END log_reg_token] + Log.d(TAG, "got token: $token") + + // As an optimization, store today’s date in Android cache + val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) + preferences.edit().putLong("lastDeviceRefreshDate", Date().time) + return token } + // Check to see whether this device's registration token was refreshed within the last month. Refresh if not. + private fun dateRefresh() { + lifecycleScope.launch { + val refreshDate = (Firebase.firestore.collection("refresh") + .document("refreshDate").get().await().data!!["lastRefreshDate"] as Timestamp) + val deviceRefreshDate = (Firebase.firestore.collection("fcmTokens") + .document("myuserid").get().await().data!!["timestamp"] as Timestamp) + if (deviceRefreshDate < refreshDate) { + getAndStoreRegToken() + } + } + } + + /* + As an optimization to prevent Firestore calls every time the device opens the app, store the last all-devices + refresh date (lastGlobalRefresh) and this particular device's last refresh date (lastDeviceRefresh) into + Android's SharedPreferences. + + If lastDeviceRefresh is before lastGlobalRefresh, update the device's registration token, and store it into + Firestore and SharedPreferencs. Also, if today's date is a month after lastGlobalRefresh, sync lastGlobalRefresh + in SharedPreferences with Firestore's lastGlobalRefresh. + */ + private fun optimizedDateRefresh() { + val preferences = this.getPreferences(Context.MODE_PRIVATE) + // Refresh date (stored as milliseconds, SharedPreferences cannot store Date) that ensures token freshness + val lastGlobalRefreshLong = preferences.getLong("lastGlobalRefreshDate", -1) + val lastGlobalRefresh = Date(lastGlobalRefreshLong) + // Date of last refresh of device’s registration token + val lastDeviceRefreshLong = preferences.getLong("lastDeviceRefreshDate", -1) + val lastDeviceRefresh = Date(lastDeviceRefreshLong) + lifecycleScope.launch { + if (lastDeviceRefreshLong == -1L || lastGlobalRefreshLong == -1L + || lastDeviceRefresh.before(lastGlobalRefresh)) { + // Get token, store into Firestore, and update cache + getAndStoreRegToken() + preferences.edit().putLong("lastGlobalRefreshDate", lastDeviceRefresh.time) + } + + // Check if today is more than one month beyond cached global refresh date + // and if so, sync date with Firestore and update cache + val today = Date() + val c = Calendar.getInstance().apply { + time = if (lastGlobalRefreshLong == -1L) today else lastGlobalRefresh + add(Calendar.DATE, 30) + } + + if (lastGlobalRefreshLong == -1L || today.after(c.time)) { + val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() + val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 + preferences.edit().putLong("lastGlobalRefreshDate", updatedTime) + } + } + } + companion object { - private const val TAG = "MainActivity" + private const val TAG = "MainActivityandreawu" } } diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index a0642bd28..bfa153e68 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -7,6 +7,7 @@ import android.content.Context import android.content.Intent import android.media.RingtoneManager import android.os.Build +import android.preference.PreferenceManager import android.util.Log import androidx.core.app.NotificationCompat import androidx.work.OneTimeWorkRequest @@ -17,6 +18,8 @@ import com.google.firebase.ktx.Firebase import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.google.firebase.quickstart.fcm.R +import java.util.* + class MyFirebaseMessagingService : FirebaseMessagingService() { @@ -117,6 +120,10 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { // Get user ID from Firebase Auth or your own server Firebase.firestore.collection("fcmTokens").document("myuserid") .set(deviceToken) + + // As an optimization, store today’s date in Android cache + val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) + preferences.edit().putLong("lastDeviceRefreshDate", Date().time) } /** From 248b3e603c31e25d2191d02dafcebcb69f18574a Mon Sep 17 00:00:00 2001 From: andreaowu Date: Wed, 15 Feb 2023 23:08:01 +0700 Subject: [PATCH 11/13] use flag for whether to use optimized version or not --- .../quickstart/fcm/kotlin/MainActivity.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index dc047f5f6..cc95aaddd 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -27,6 +27,8 @@ import java.util.Date class MainActivity : AppCompatActivity() { + val IS_OPTIMIZE = true + private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> @@ -100,9 +102,11 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() askNotificationPermission() - - dateRefresh() - optimizedDateRefresh() // optimized version of dateRefresh() that requires using Android SharedPreferences + if (IS_OPTIMIZE) { + dateRefresh() + } else { + optimizedDateRefresh() // optimized version of dateRefresh() that uses Android SharedPreferences + } } private fun askNotificationPermission() { @@ -135,8 +139,10 @@ class MainActivity : AppCompatActivity() { Log.d(TAG, "got token: $token") // As an optimization, store today’s date in Android cache - val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) - preferences.edit().putLong("lastDeviceRefreshDate", Date().time) + if (IS_OPTIMIZE) { + val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) + preferences.edit().putLong("lastDeviceRefreshDate", Date().time) + } return token } From d9cd34b27f9934057c31ebf7a9a8ec14243a6b0f Mon Sep 17 00:00:00 2001 From: andreaowu Date: Tue, 11 Apr 2023 11:25:25 +0900 Subject: [PATCH 12/13] update code --- messaging/app/build.gradle | 1 + .../quickstart/fcm/kotlin/MainActivity.kt | 104 +++++------------- .../fcm/kotlin/MyFirebaseMessagingService.kt | 9 +- .../fcm/kotlin/UpdateTokenWorker.kt | 35 ++++++ messaging/functions/index.js | 26 +---- 5 files changed, 73 insertions(+), 102 deletions(-) create mode 100644 messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/UpdateTokenWorker.kt diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index c3723d81e..4e0cf81cf 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.work:work-runtime-ktx:2.8.0' // Required when asking for permission to post notifications (starting in Android 13) implementation 'androidx.activity:activity-ktx:1.6.1' diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt index cc95aaddd..de660c366 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt @@ -13,6 +13,9 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager import com.google.firebase.Timestamp import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.ktx.firestore @@ -24,11 +27,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import java.util.Calendar import java.util.Date +import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { - val IS_OPTIMIZE = true - private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> @@ -102,11 +104,12 @@ class MainActivity : AppCompatActivity() { Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() askNotificationPermission() - if (IS_OPTIMIZE) { - dateRefresh() - } else { - optimizedDateRefresh() // optimized version of dateRefresh() that uses Android SharedPreferences - } + // Refresh token and send to server every month + val saveRequest = + PeriodicWorkRequestBuilder(730, TimeUnit.HOURS) + .build() + + WorkManager.getInstance(this).enqueueUniquePeriodicWork("saveRequest", ExistingPeriodicWorkPolicy.UPDATE, saveRequest); } private fun askNotificationPermission() { @@ -126,79 +129,32 @@ class MainActivity : AppCompatActivity() { private suspend fun getAndStoreRegToken(): String { // [START log_reg_token] var token = Firebase.messaging.token.await() - // Add token and timestamp to Firestore for this user - val deviceToken = hashMapOf( - "token" to token, - "timestamp" to FieldValue.serverTimestamp(), - ) - - // Get user ID from Firebase Auth or your own server - Firebase.firestore.collection("fcmTokens").document("myuserid") - .set(deviceToken).await() - // [END log_reg_token] - Log.d(TAG, "got token: $token") - - // As an optimization, store today’s date in Android cache - if (IS_OPTIMIZE) { - val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) - preferences.edit().putLong("lastDeviceRefreshDate", Date().time) - } - - return token - } - - // Check to see whether this device's registration token was refreshed within the last month. Refresh if not. - private fun dateRefresh() { - lifecycleScope.launch { - val refreshDate = (Firebase.firestore.collection("refresh") - .document("refreshDate").get().await().data!!["lastRefreshDate"] as Timestamp) - val deviceRefreshDate = (Firebase.firestore.collection("fcmTokens") - .document("myuserid").get().await().data!!["timestamp"] as Timestamp) - if (deviceRefreshDate < refreshDate) { - getAndStoreRegToken() - } - } - } - - /* - As an optimization to prevent Firestore calls every time the device opens the app, store the last all-devices - refresh date (lastGlobalRefresh) and this particular device's last refresh date (lastDeviceRefresh) into - Android's SharedPreferences. - If lastDeviceRefresh is before lastGlobalRefresh, update the device's registration token, and store it into - Firestore and SharedPreferencs. Also, if today's date is a month after lastGlobalRefresh, sync lastGlobalRefresh - in SharedPreferences with Firestore's lastGlobalRefresh. - */ - private fun optimizedDateRefresh() { + // Check whether the retrieved token matches the one on your server for this user's device val preferences = this.getPreferences(Context.MODE_PRIVATE) - // Refresh date (stored as milliseconds, SharedPreferences cannot store Date) that ensures token freshness - val lastGlobalRefreshLong = preferences.getLong("lastGlobalRefreshDate", -1) - val lastGlobalRefresh = Date(lastGlobalRefreshLong) - // Date of last refresh of device’s registration token - val lastDeviceRefreshLong = preferences.getLong("lastDeviceRefreshDate", -1) - val lastDeviceRefresh = Date(lastDeviceRefreshLong) + val tokenStored = preferences.getString("deviceToken", "") lifecycleScope.launch { - if (lastDeviceRefreshLong == -1L || lastGlobalRefreshLong == -1L - || lastDeviceRefresh.before(lastGlobalRefresh)) { - // Get token, store into Firestore, and update cache - getAndStoreRegToken() - preferences.edit().putLong("lastGlobalRefreshDate", lastDeviceRefresh.time) + if (tokenStored == "" || tokenStored != token) + { + // If you have your own server, call API to send the above token and Date() for this user's device + + // Example shown below with Firestore + // Add token and timestamp to Firestore for this user + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken).await() } + } - // Check if today is more than one month beyond cached global refresh date - // and if so, sync date with Firestore and update cache - val today = Date() - val c = Calendar.getInstance().apply { - time = if (lastGlobalRefreshLong == -1L) today else lastGlobalRefresh - add(Calendar.DATE, 30) - } + // [END log_reg_token] + Log.d(TAG, "got token: $token") - if (lastGlobalRefreshLong == -1L || today.after(c.time)) { - val document = Firebase.firestore.collection("refresh").document("refreshDate").get().await() - val updatedTime = (document.data!!["lastRefreshDate"] as Timestamp).seconds * 1000 - preferences.edit().putLong("lastGlobalRefreshDate", updatedTime) - } - } + return token } companion object { diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index bfa153e68..29192a5dd 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -110,20 +110,17 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { * @param token The new token. */ private fun sendTokenToServer(token: String?) { - // TODO: Implement this method to send token to your app server. + // If you're running your own server, call API to send token and today's date for the user + + // Example shown below with Firestore // Add token and timestamp to Firestore for this user val deviceToken = hashMapOf( "token" to token, "timestamp" to FieldValue.serverTimestamp(), ) - // Get user ID from Firebase Auth or your own server Firebase.firestore.collection("fcmTokens").document("myuserid") .set(deviceToken) - - // As an optimization, store today’s date in Android cache - val preferences = this.getSharedPreferences("default", Context.MODE_PRIVATE) - preferences.edit().putLong("lastDeviceRefreshDate", Date().time) } /** diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/UpdateTokenWorker.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/UpdateTokenWorker.kt new file mode 100644 index 000000000..37f855e7b --- /dev/null +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/UpdateTokenWorker.kt @@ -0,0 +1,35 @@ +package com.google.firebase.quickstart.fcm.kotlin + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.google.firebase.firestore.FieldValue +import com.google.firebase.firestore.ktx.firestore +import com.google.firebase.ktx.Firebase +import com.google.firebase.messaging.ktx.messaging +import kotlinx.coroutines.tasks.await + +class UpdateTokenWorker(appContext: Context, workerParams: WorkerParameters): + CoroutineWorker(appContext, workerParams) { + + override suspend fun doWork(): Result { + // Refresh the token and send it to your server + var token = Firebase.messaging.token.await() + + // If you have your own server, call API to send the above token and Date() for this user's device + + // Example shown below with Firestore + // Add token and timestamp to Firestore for this user + val deviceToken = hashMapOf( + "token" to token, + "timestamp" to FieldValue.serverTimestamp(), + ) + + // Get user ID from Firebase Auth or your own server + Firebase.firestore.collection("fcmTokens").document("myuserid") + .set(deviceToken).await() + + // Indicate whether the work finished successfully with the Result + return Result.success() + } +} \ No newline at end of file diff --git a/messaging/functions/index.js b/messaging/functions/index.js index 535eed8f2..0ef878b42 100644 --- a/messaging/functions/index.js +++ b/messaging/functions/index.js @@ -5,18 +5,7 @@ const admin = require('firebase-admin'); admin.initializeApp(); -const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days - -/** - * Scheduled function that runs once a month. It updates the last refresh date for - * tokens so that a client can refresh the token if the last time it did so was - * before the refresh date. - */ -// [START refresh_date_scheduled_function] -exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((context) => { - admin.firestore().doc('refresh/refreshDate').set({ lastRefreshDate : Date.now() }); -}); -// [END refresh_date_scheduled_function] +const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60; // 60 days /** * Scheduled function that runs once a day. It retrieves all stale tokens then @@ -27,18 +16,11 @@ exports.scheduledFunction = functions.pubsub.schedule('0 0 1 * *').onRun((contex */ // [START remove_stale_tokens] exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { + // Get all documents where the timestamp exceeds is not within the past month const staleTokensResult = await admin.firestore().collection('fcmTokens') .where("timestamp", "<", Date.now() - EXPIRATION_TIME) .get(); - - const staleTokens = staleTokensResult.docs.map(staleTokenDoc => staleTokenDoc.id); - - await admin.messaging().unsubscribeFromTopic(staleTokens, 'weather'); - - const deletePromises = []; - for (const staleTokenDoc of staleTokensResult.docs) { - deletePromises.push(staleTokenDoc.ref.delete()); - } - await Promise.all(deletePromises); + // Delete devices with stale tokens + staleTokensResult.forEach(function(doc) { doc.ref.delete(); }); }); // [END remove_stale_tokens] From 805745202c8618e55785e429cb168c42f2e5912b Mon Sep 17 00:00:00 2001 From: Cynthia Wang Date: Wed, 12 Jul 2023 15:58:49 -0400 Subject: [PATCH 13/13] fix: add tag --- .../quickstart/fcm/kotlin/MyFirebaseMessagingService.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt index 29192a5dd..f337b4d4e 100644 --- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt +++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MyFirebaseMessagingService.kt @@ -101,6 +101,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { Log.d(TAG, "Short lived task is done.") } + // [START send_token_to_server] /** * Persist token to third-party servers. * @@ -122,6 +123,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { Firebase.firestore.collection("fcmTokens").document("myuserid") .set(deviceToken) } + // [END send_token_to_server] /** * Create and show a simple notification containing the received FCM message.