Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sample implementation of registration tokens for FCM #1453

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
7 changes: 7 additions & 0 deletions messaging/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
andreaowu marked this conversation as resolved.
Show resolved Hide resolved

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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {

Expand Down Expand Up @@ -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<UpdateTokenWorker>(730, TimeUnit.HOURS)
.build()

WorkManager.getInstance(this).enqueueUniquePeriodicWork("saveRequest", ExistingPeriodicWorkPolicy.UPDATE, saveRequest);
}

private fun askNotificationPermission() {
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid wildcard imports



class MyFirebaseMessagingService : FirebaseMessagingService() {

Expand Down Expand Up @@ -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]

Expand All @@ -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.
*
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
26 changes: 26 additions & 0 deletions messaging/functions/index.js
Original file line number Diff line number Diff line change
@@ -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]
1 change: 1 addition & 0 deletions messaging/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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