diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle index f6f6f8270..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' @@ -70,8 +71,14 @@ dependencies { implementation 'com.google.firebase:firebase-installations-ktx:17.1.0' + // 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/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..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 @@ -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,22 @@ 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 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 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.launch +import kotlinx.coroutines.tasks.await +import java.util.Calendar +import java.util.Date +import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { @@ -79,26 +91,25 @@ 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 - } - + lifecycleScope.launch { // Get new FCM registration token - val token = task.result - + 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() - }) - // [END log_reg_token] + } } Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show() askNotificationPermission() + + // 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() { @@ -115,8 +126,39 @@ class MainActivity : AppCompatActivity() { } } + private suspend fun getAndStoreRegToken(): String { + // [START log_reg_token] + var token = Firebase.messaging.token.await() + + // Check whether the retrieved token matches the one on your server for this user's device + val preferences = this.getPreferences(Context.MODE_PRIVATE) + val tokenStored = preferences.getString("deviceToken", "") + lifecycleScope.launch { + 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() + } + } + + // [END log_reg_token] + Log.d(TAG, "got token: $token") + + return token + } + 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 4828253c2..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 @@ -7,13 +7,19 @@ 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 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 +import java.util.* + class MyFirebaseMessagingService : FirebaseMessagingService() { @@ -74,7 +80,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) + sendTokenToServer(token) } // [END on_new_token] @@ -95,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. * @@ -103,10 +110,20 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { * * @param token The new token. */ - private fun sendRegistrationToServer(token: String?) { - // TODO: Implement this method to send token to your app server. - Log.d(TAG, "sendRegistrationTokenToServer($token)") + private fun sendTokenToServer(token: String?) { + // 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) } + // [END send_token_to_server] /** * Create and show a simple notification containing the received FCM message. 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 new file mode 100644 index 000000000..0ef878b42 --- /dev/null +++ b/messaging/functions/index.js @@ -0,0 +1,26 @@ +'use strict'; + +const functions = require('firebase-functions'); +const admin = require('firebase-admin'); + +admin.initializeApp(); + +const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60; // 60 days + +/** + * 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) => { + // 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(); + // Delete devices with stale tokens + staleTokensResult.forEach(function(doc) { doc.ref.delete(); }); +}); +// [END remove_stale_tokens] 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