Main Activity
package com.example.pone
import android.content.ContentValues.TAG
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
const val TOPIC = "/topics/myTopic2"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseService.sharedPref = getSharedPreferences("sharedPref", Context.MODE_PRIVATE)
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (task.isSuccessful) {
if (task.result != null && !TextUtils.isEmpty(task.result)) {
val token: String = task.result!!
etToken.setText(token.toString())
}
}
}
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
btnSend.setOnClickListener {
val title = etTitle.text.toString()
val message = etMessage.text.toString()
val recipientToken = etToken.text.toString()
if(title.isNotEmpty() && message.isNotEmpty() && recipientToken.isNotEmpty()) {
PushNotification(
NotificationData(title, message),
recipientToken
).also {
sendNotification(it)
}
}
}
}
private fun sendNotification(notification: PushNotification) = CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitInstance.api.postNotification(notification)
if(response.isSuccessful) {
Log.d(TAG, "Response: ${Gson().toJson(response)}")
} else {
Log.e(TAG, response.errorBody().toString())
}
} catch(e: Exception) {
Log.e(TAG, e.toString())
}
}
}
Firebase Service Class
package com.example.pone
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_ONE_SHOT
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlin.random.Random
private const val CHANNEL_ID = "my_channel"
class FirebaseService : FirebaseMessagingService() {
companion object {
var sharedPref: SharedPreferences? = null
var token: String?
get() {
return sharedPref?.getString("token", "")
}
set(value) {
sharedPref?.edit()?.putString("token", value)?.apply()
}
}
override fun onNewToken(newToken: String) {
super.onNewToken(newToken)
token = newToken
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val intent = Intent(this, MainActivity::class.java)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationID = Random.nextInt()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(notificationManager)
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_ONE_SHOT)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(message.data["title"])
.setContentText(message.data["message"])
.setSmallIcon(R.drawable.common_google_signin_btn_icon_dark)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
notificationManager.notify(notificationID, notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(notificationManager: NotificationManager) {
val channelName = "channelName"
val channel = NotificationChannel(CHANNEL_ID, channelName, IMPORTANCE_HIGH).apply {
description = "My channel description"
enableLights(true)
lightColor = Color.GREEN
}
notificationManager.createNotificationChannel(channel)
}
}
PushNotification Data Class
data class PushNotification(
val data: NotificationData,
val to: String
)
NotificationAPI interface
import com.example.pone.Constants.Companion.CONTENT_TYPE
import com.example.pone.Constants.Companion.SERVER_KEY
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface NotificationAPI {
@Headers("Authorization: key=$SERVER_KEY", "Content-Type:$CONTENT_TYPE")
@POST("fcm/send")
suspend fun postNotification(
@Body notification: PushNotification
): Response<ResponseBody>
}
Notification Data Class
data class NotificationData(
val title: String,
val message: String
)
RetroFitIntance class
import com.example.pone.Constants.Companion.BASE_URL
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class RetrofitInstance {
companion object {
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val api by lazy {
retrofit.create(NotificationAPI::class.java)
}
}
}
Constants
class Constants {
companion object {
const val BASE_URL = "https://fcm.googleapis.com"
const val SERVER_KEY = "*****"
const val CONTENT_TYPE = "application/json"
}
}
I am receiving messages on emulator but not real device. I am able to send messages to emulator from real device but not receiving messages from emulator to real device.
Update
When I try to send message to real device itself, this error happens
2022-09-18 00:54:34.087 8325-8423/com.example.pone E/AndroidRuntime: FATAL EXCEPTION: Firebase-Messaging-Intent-Handle
Process: com.example.pone, PID: 8325
java.lang.IllegalArgumentException: com.example.pone: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
at android.app.PendingIntent.getActivityAsUser(PendingIntent.java:458)
at android.app.PendingIntent.getActivity(PendingIntent.java:444)
at android.app.PendingIntent.getActivity(PendingIntent.java:408)
at com.example.pone.FirebaseService.onMessageReceived(FirebaseService.kt:52)
at com.google.firebase.messaging.FirebaseMessagingService.dispatchMessage(FirebaseMessagingService.java:235)
at com.google.firebase.messaging.FirebaseMessagingService.passMessageIntentToSdk(FirebaseMessagingService.java:185)
at com.google.firebase.messaging.FirebaseMessagingService.handleMessageIntent(FirebaseMessagingService.java:172)
at com.google.firebase.messaging.FirebaseMessagingService.handleIntent(FirebaseMessagingService.java:161)
at com.google.firebase.messaging.EnhancedIntentService.lambda$processIntent$0$EnhancedIntentService(EnhancedIntentService.java:78)
at com.google.firebase.messaging.EnhancedIntentService$$ExternalSyntheticLambda1.run(Unknown Source:6)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at com.google.android.gms.common.util.concurrent.zza.run(com.google.android.gms:play-services-basement@@18.1.0:2)
at java.lang.Thread.run(Thread.java:920)
2
Answers
Update
Fixed
Inside
FirebaseServiceClass
replaceFLAG_ONE_SHOT
withFLAG_MUTABLE
This is kind of a bug of firebase. try after sometime, you will receive the fcm message on real device too.