Firebase ํ๋ก์ ํธ ๋ง๋ค๊ธฐ ๋ฐ Firebase์ ์ฑ ๋ฑ๋ก ํ๋ ๋ถ๋ถ์ ์ฐธ๊ณ ํ ์ ์๋ ์์ ๊ฐ ๋ง๊ธฐ์ ๋ณธ ํฌ์คํ ์์๋ ์๋ตํ๋ ค๊ณ ํ๋ค.
Firebase ๋ฌธ์์ 'Firebase ํ๋ก์ ํธ์ Firebase ์ถ๊ฐ' ๋ถ๋ถ์ ์ฐธ๊ณ ํด๋ ์ข๋ค.
Android Studio ๋ด์์ 'Tools > Firebase'๋ฅผ ํด๋ฆญํ๋ฉด ํ๋ฉด ์ฐ์ธก์ ๋ค์๊ณผ ๊ฐ์ ํ๋ฉด์ด ๋ํ๋๋๋ฐ, 'Cloud Messaging'์ ํด๋ฆญํ๋ค.
(1) Connect your app to Firebase ์ (2) Add FCM to your app์ ํด๋ฆญํด Firebase์ ์ฐ๊ฒฐ์ ์งํํ๋ค.
์ฐ๊ฒฐ์ด ์๋ฃ๋๋ฉด ์๋ ํ๋ฉด๊ณผ ๊ฐ์ด ํ์ ๋๋ค.
build.gradle(์ฑ ์์ค)์์ Firebase ์ธ์ฆ Android ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ข ์ ํญ๋ชฉ์ ์ถ๊ฐํ๋ค.
dependencies {
implementation platform('com.google.firebase:firebase-bom:31.3.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-messaging:23.0.3'
}
๐ ์ฑ์ ๋งค๋ํ์คํธ์ FirebaseMessagingService๋ฅผ ํ์ฅํ๋ ์๋น์ค ์ถ๊ฐ
์ด ์๋น์ค๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฑ์ ์๋ฆผ์ ์์ ํ๋ ๊ฒ ์ธ์ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ ค๋ ๊ฒฝ์ฐ์ ํ์ํ๋ค. ํฌ๊ทธ๋ผ์ด๋ ์ฑ์ ์๋ฆผ ์์ , ๋ฐ์ดํฐ ํ์ด๋ก๋ ์์ , ์ ์คํธ๋ฆผ ๋ฉ์์ง ์ ์ก ๋ฑ์ ์ํํ๋ ค๋ฉด ์ด ์๋น์ค๋ฅผ ํ์ฅํด์ผ ํ๋ค.
<service
android:name=".fcm.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
๐ก Android 13(API ์์ค 33)์์๋ ์ฑ์์ ์์ธ ์๋ ์๋ฆผ์ ๋ณด๋ด๊ธฐ ์ํ ์๋ก์ด ๋ฐํ์ ๊ถํ POST_NOTIFICATIONS๋ฅผ ๋์ ํ๋ค.
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
</application>
</manifest>
class FCMActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val PERMISSION_REQUEST_CODE = 5000
private fun permissionCheck() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissionCheck = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.POST_NOTIFICATIONS
)
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
PERMISSION_REQUEST_CODE
)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_fcm)
permissionCheck()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(applicationContext, "Permission is denied", Toast.LENGTH_SHORT)
.show()
} else {
Toast.makeText(applicationContext, "Permission is granted", Toast.LENGTH_SHORT)
.show()
}
}
}
}
}
์์์ 'FirebaseMessagingService' ๋ฅผ ํ์ฅํ๋ ์๋น์ค๋ฅผ ์ถ๊ฐํ์ฌ ํด๋น ์๋น์ค๋ฅผ ๋ฑ๋กํ๋ ์ ์ฐจ๋ฅผ ์งํํ์๋ค.
FirebaseMessagingService๋ฅผ ์์ํ๋ MyFirebaseMessagingService ํด๋์ค๋ฅผ ์์ฑํ๋ค.
class MyFirebaseMessagingService : FirebaseMessagingService() {
// ๋ฉ์์ง๋ฅผ ์์ ํ ๋ ํธ์ถ ๋๋ค.
// ์์ ๋ RemoteMessage ๊ฐ์ฒด๋ฅผ ๊ธฐ์ค์ผ๋ก ์์
์ ์ํํ๊ณ ๋ฉ์์ง ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
// ๋ฉ์์ง์ ๋ฐ์ดํฐ ํ์ด๋ก๋๊ฐ ํฌํจ ๋์ด ์๋์ง ํ์ธํ๋ค.
// ํ์ด๋ก๋๋ ์ ์ก๋ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํ๋ค.
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
sendNotification(
remoteMessage.data["title"].toString(),
remoteMessage.data["body"].toString()
)
} else {
// ๋ฉ์์ง์ ์๋ฆผ ํ์ด๋ก๋๊ฐ ํฌํจ๋์ด ์๋์ง ํ์ธํ๋ค.
remoteMessage.notification?.let {
sendNotification(
remoteMessage.notification!!.title.toString(),
remoteMessage.notification!!.body.toString()
)
}
}
}
// ์ ํ ํฐ์ด ์์ฑ๋ ๋๋ง๋ค onNewToken ์ฝ๋ฐฑ์ด ํธ์ถ๋๋ค.
// ๋ฑ๋ก ํ ํฐ์ด ์ฒ์ ์์ฑ๋๋ฏ๋ก ์ฌ๊ธฐ์ ํ ํฐ์ ๊ฒ์ํ ์ ์๋ค.
override fun onNewToken(token: String) {
super.onNewToken(token)
sendRegistrationToServer(token)
}
// ๋ฉ์์ง์ ๋ฐ์ดํฐ ํ์ด๋ก๋๊ฐ ํฌํจ ๋์ด ์์ ๋ ์คํ๋๋ ๋ฉ์๋
// ์ฅ์๊ฐ ์คํ (10์ด ์ด์) ์์
์ ๊ฒฝ์ฐ WorkManager๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์์
์ ์์ฝํ๋ค.
private fun scheduleJob() {
val work = OneTimeWorkRequest.Builder(MyWorker::class.java)
.build()
WorkManager.getInstance(this)
.beginWith(work)
.enqueue()
}
// ๋ฉ์์ง์ ๋ฐ์ดํฐ ํ์ด๋ก๋๊ฐ ํฌํจ ๋์ด ์์ ๋ ์คํ๋๋ ๋ฉ์๋
// 10์ด ์ด๋ด๋ก ๊ฑธ๋ฆด ๋ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ค.
private fun handleNow() {
Log.d(TAG, "Short lived task is done.")
}
// ํ์ฌ ์๋ฒ์ ํ ํฐ์ ์ ์งํด์ฃผ๋ ๋ฉ์๋์ด๋ค.
private fun sendRegistrationToServer(token: String?) {
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
// ์์ ๋ FCM ๋ฉ์์ง๋ฅผ ํฌํจํ๋ ๊ฐ๋จํ ์๋ฆผ์ ๋ง๋ค๊ณ ํ์ํ๋ค.
private fun sendNotification(title: String, body: String) {
val intent = Intent(this, FCMActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_IMMUTABLE
)
val channelId = "fcm_default_channel"
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// ์ค๋ ์ค ์ด์์์ ์๋ฆผ์ ์ ๊ณตํ๋ ค๋ฉด ์ฑ์ ์๋ฆผ ์ฑ๋์ ์์คํ
์ ๋ฑ๋กํด์ผ ํ๋ค.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(0, notificationBuilder.build())
}
companion object {
private const val TAG = "MyFirebaseMsgService"
}
internal class MyWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
}
์กํฐ๋นํฐ์ ํ์ฌ ๋ฑ๋ก ๋ ํ ํฐ์ ๊ฐ์ ธ์ค๋ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ค.
๐ก ํ์ฌ ํ ํฐ์ ๊ฐ์ ธ์ค๋ ค๋ฉด FirebaseMessaging.getInstance().getToken()์ ํธ์ถํ๋ค.
๐ ๊ธฐ๊ธฐ ๋ฑ๋ก ํ ํฐ ์ก์ธ์ค
FCM SDK๋ ์ฑ์ ์ฒ์ ์์ํ ๋ ํด๋ผ์ด์ธํธ ์ฑ ์ธ์คํด์ค์ฉ ๋ฑ๋ก ํ ํฐ์ ์์ฑํ๋ค.
๋จ์ผ ๊ธฐ๊ธฐ๋ฅผ ํ๊ฒํ ํ๊ฑฐ๋ ๊ธฐ๊ธฐ ๊ทธ๋ฃน์ ๋ง๋ค๋ ค๋ฉด FirebaseMessagingService๋ฅผ ํ์ฅํ๊ณ onNewToken์ ์ฌ์ ์ํ์ฌ ์ด ํ ํฐ์ ์ก์ธ์คํด์ผ ํ๋ค.
- ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ๋ฑ๋ก ํ ํฐ์ด ๋ณ๊ฒฝ๋ ์ ์๋ค.
- ์ ๊ธฐ๊ธฐ์์ ์ฑ ๋ณต์
- ์ฌ์ฉ์๊ฐ ์ฑ ์ ๊ฑฐ/์ฌ์ค์น
- ์ฌ์ฉ์๊ฐ ์ฑ ๋ฐ์ดํฐ ์๊ฑฐ
class FCMActivity : AppCompatActivity() {
companion object {
private val PERMISSION_REQUEST_CODE = 5000
private val TAG = "FCMActivity"
}
private lateinit var binding: ActivityFcmBinding
private fun permissionCheck() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissionCheck = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.POST_NOTIFICATIONS
)
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
PERMISSION_REQUEST_CODE
)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_fcm)
permissionCheck()
// ํ์ฌ ํ ํฐ์ ๊ฐ์ ธ์ค๋ ค๋ฉด
// FirebaseMessaging.getInstace().getToken()์ ํธ์ถํ๋ค.
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
// FCM ๋ฑ๋ก ํ ํฐ ๊ฐ์ ธ์ค๊ธฐ
val token = task.result
val msg = "FCM Registration token: " + token;
Log.d(TAG, msg)
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
})
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(applicationContext, "Permission is denied", Toast.LENGTH_SHORT)
.show()
} else {
Toast.makeText(applicationContext, "Permission is granted", Toast.LENGTH_SHORT)
.show()
}
}
}
}
}
Firebase Console์ ์๋ฆผ ์์ฑ ํ๋ฉด์์ ์ ๋ชฉ๊ณผ ์๋ฆผ ํ ์คํธ๋ง ์ ๋ ฅํ์ฌ ์ ์กํ์ผ๋ฉฐ, ๋ค์ ํ๋ฉด๊ณผ ๊ฐ์ด ์๋ฆผ ๋ฉ์์ง๊ฐ ์ ์ก๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.