From 4a400fb33c49308f965778e9728cb29f77af5e53 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Mon, 13 Feb 2023 11:04:25 +0000 Subject: [PATCH 1/3] add snippets to demonstrate fcm registration tokens best practices --- messaging/app/build.gradle | 6 +++ .../example/messaging/kotlin/MainActivity.kt | 46 +++++++++++++++++++ messaging/functions/index.js | 44 ++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 messaging/functions/index.js diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index 6381aaed9..400ba68c8 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -30,8 +30,14 @@ dependencies { // for Google Analytics. This is recommended, but not required. implementation 'com.google.firebase:firebase-analytics:21.2.0' + // Used to store FCM Registration Token. + // This is recommended, but not required. + // See: https://firebase.google.com/docs/cloud-messaging/manage-tokens + implementation 'com.google.firebase:firebase-firestore-ktx:24.4.3' + implementation "com.google.android.gms:play-services-auth:20.4.1" implementation 'androidx.work:work-runtime-ktx:2.7.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' } apply plugin: 'com.google.gms.google-services' diff --git a/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt index f899ee848..b7c89d123 100644 --- a/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt @@ -1,6 +1,7 @@ package com.google.firebase.example.messaging.kotlin import android.Manifest +import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.os.Bundle @@ -10,11 +11,19 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.google.firebase.Timestamp import com.google.firebase.example.messaging.MainActivity +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.messaging.ktx.remoteMessage +import java.util.Calendar +import java.util.Date import java.util.concurrent.atomic.AtomicInteger +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await class MainActivity : AppCompatActivity() { @@ -130,4 +139,41 @@ class MainActivity : AppCompatActivity() { } // [END ask_post_notifications] + // [START get_store_token] + private suspend fun getAndStoreRegToken(): String { + val 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 + } + // [END get_store_token] + + private fun refreshToken() { + // 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).apply() + } + } + } } diff --git a/messaging/functions/index.js b/messaging/functions/index.js new file mode 100644 index 000000000..23f8d70dc --- /dev/null +++ b/messaging/functions/index.js @@ -0,0 +1,44 @@ +'use strict'; + +const functions = require('firebase-functions'); +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] + +/** + * Scheduled function that runs once a day. It retrieves all stale tokens then + * unsubscribes them from 'topic1' then deletes them. + * + * 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) + .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); +}); +// [END remove_stale_tokens] \ No newline at end of file From 5b864dd9abfb4ebc8baa5a8b2414ab1d51034196 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Mon, 13 Feb 2023 11:11:24 +0000 Subject: [PATCH 2/3] chore(messaging): enable multidex --- messaging/app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index 400ba68c8..39164501d 100644 --- a/messaging/app/build.gradle +++ b/messaging/app/build.gradle @@ -11,6 +11,7 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true } buildTypes { release { From 22cdad614cd2d81a99bf67a7470d82fd4dbd57c7 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Mon, 13 Feb 2023 14:29:53 +0000 Subject: [PATCH 3/3] remove unused logic --- .../example/messaging/kotlin/MainActivity.kt | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt index b7c89d123..2bc91bc64 100644 --- a/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt +++ b/messaging/app/src/main/java/com/google/firebase/example/messaging/kotlin/MainActivity.kt @@ -155,25 +155,4 @@ class MainActivity : AppCompatActivity() { } // [END get_store_token] - private fun refreshToken() { - // 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).apply() - } - } - } }