구현하려는 것:
내 게시물에 누군가가 댓글을 남기면 나에게 알림이 오게한다.
또, 그 알림을 누르면 해당 게시물로 이동한다.
프로젝트 설정에 대한 자세한 설명은 공식문서를 참고하기 바란다.
firebase.google.com/docs/cloud-messaging/android/client?authuser=0
만약 A가 B의 게시물에 댓글을 달았고, B에게 FCM을 보내려면 B에게 발급된 토큰이 어떤 것인지 알아야한다.
여기서 토큰은 파이어베이스 메시징을 위해 사용되는 앱 인스턴스용 등록 토큰을 말한다.
이 토큰을 통해 파이어베이스는 메시징을 보낼 앱 인스턴스를 구분한다.
먼저, 토큰을 얻는다.
//파이어베이스 클라우드 메시징을 위한 토큰 요청
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(Constants.LOG_TAG, "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
// Get new FCM registration token
val token = task.result
//웹서버에 토큰 등록
mUserViewModel.registerFirebaseToken(user.mEmail, token!!);
})
웹서버에 토큰을 등록하는 이유는 어떤 앱 인스턴스 혹은 계정이 어떤 토큰을 받았는지 확인할 수 있어야 하기 때문이다.
registerFirebaseToken은 웹서버로 토큰과 유저 이메일을 담은 post 요청을 날려서 , 토큰을 데이터베이스의 유저정보에 추가한다.
이제 FCM을 앱에서 받으면 된다.
제일먼저 FirebaseMessagingService를 상속받는 클래스를 만들자.
class MyFirebaseMessagingService : FirebaseMessagingService() {
}
onCreate에서 노티피케이션 채널을 만들고 노티피케이션매니저에 등록하자.
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
createNotificationChannel(mNotificationManager)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(notificationManager: NotificationManager) {
val notificationChannel = NotificationChannel(
Constants.NOTIFICATION_CHANNEL_ID,
Constants.NOTIFICATION_CHANNEL_NAME,
IMPORTANCE_LOW //LOW로 해줘야 진동안울림
)
notificationManager.createNotificationChannel(notificationChannel);
}
이제 onMessageReceived()를 구현하자.
/**
* 웹서버에서 FCM을 요청하고, 파이어베이스에서 이 앱 인스턴스에 FCM을 보내면 호출된다.
* @param p0 웹서버에서 FCM 요청과 함께보낸 데이터.
*/
override fun onMessageReceived(p0: RemoteMessage) {
super.onMessageReceived(p0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
/*
* p0에는 notification 객체와 메시지에 포함된 데이터(웹서버측에서 보낸 데이터다)가 있다.
* 이것들을 가지고 노티피케이션을 수행하자.
*/
notifyNotification(
p0.notification?.title, /*노티피케이션의 타이틀*/
p0.notification?.body, /*노티피케이션의 내용*/
p0.data["topic_id"] ?: "0", /*메시지의 데이터*/
p0.data["picture_id"] ?: "0"
);
}
}
p0로 들어오는 메시지는 node.js 서버에서 아래처럼 보내준다.
firebase_admin.messaging().send(message) 를 통해 firebase에 FCM을 요청할 수 있다. 그러면 token을 발급받았던 앱 인스턴스에 FCM을 보내준다.
let message = {
notification: {
title: msg_title,
body: msg_body
},
data: {
topic_id: topic_id,
picture_id: picture_id
},
token: /*토큰*/
};
firebase_admin.messaging().send(message)
이제 받은 FCM의 데이터를 참조해서 실제로 notification을 notify 해보자.
/**
* 실제 노티피케이션을 실행한다.
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun notifyNotification(title: String?, content: String?, notifiedTopicId: String, notifiedPictureId: String) {
//알림을 클릭했을때 알림이 대상이되는 토픽이나 사진으로 이동하게한는 인텐트.
//인텐트 안에는 알림의 대상의 id를 담는다.
val notificationIntent = Intent(this, MainActivity::class.java).apply {
if (notifiedTopicId != "0")
putExtra(Constants.EXTRA_NOTIFIED_TOPIC_ID, notifiedTopicId)
else if (notifiedPictureId != "0")
putExtra(Constants.EXTRA_NOTIFIED_PICTURE_ID, notifiedPictureId)
}
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, FLAG_UPDATE_CURRENT
)
val notification = Notification.Builder(this, Constants.NOTIFICATION_CHANNEL_ID)
.setAutoCancel(true) //사용자가 스와이프했을때 노티피케이션을 지울것인가
.setSmallIcon(R.drawable.icon_hestia) //노티피케이션 아이콘
.setContentTitle(title) //노티피케이션 제목
.setContentText(content) //노티피케이션 본문
.setContentIntent(pendingIntent) //노티피케이션을 클릭했을때 실행할 팬딩인텐트
.setGroup(NOTIFICATION_GROUP_COMMENT) //그룹
.build()
//각각의 노티피케이션은 id가 달라야 별개의 노티피케이션으로 인식된다.
mNotificationManager.notify(Constants.NOTIFICATION_ID + mNotificationCnt++, notification)
//노티피케이션을 그룹짓는 노티피케이션
val notificationSummary = Notification.Builder(this, Constants.NOTIFICATION_CHANNEL_ID)
.setAutoCancel(true)
.setSmallIcon(R.drawable.icon_hestia)
.setContentIntent(pendingIntent)
.setGroup(NOTIFICATION_GROUP_COMMENT)
.setGroupSummary(true) //이 노티피케이션은 그룹의 부모이다.
.build()
//현존하는 노티피케이션 중 NOTIFICATION_GROUP_COMMENT에 해당하는 것들을 그룹으로 묶는다.
mNotificationManager.notify(Constants.NOTIFICATION_ID_SUMMERY, notificationSummary)
}
위 코드에 notification이 두개다. 하나는 제목과 본문이 있는 실질적인 notification이고, 그런 notification이 여러개있을때 이것들을 한번에 묶는 역할을 하는 notification이 notificationSummary다.
여기까지 했으면 구현은 끝났다.
추가적으로 나는 앱을 열었을때, 저 노티피케이션 목록의 제목들과 본문들을 가져오고 싶었는데 잘 안됬었다.
내가 notifynotification()에서 만든 notification과 앱을 열었을때 getSystemService()를 통해 받아온 notification은 다른 객체였기 때문이다. getSystemService()을 통해 notification을 가져오면 몇개의 notification이 있는지는 알 수 있지만 그 내용까지는 알 수 없었다.
때문에 나는 SharedPreference를 이용했다.
밑의 코드는 그냥 FCM으로 전해온 데이터를 SharedPreference에 저장하는 코드다.
private fun notifyNotification(title: String?, content: String?, notifiedTopicId: String, notifiedPictureId: String) {
if (title == null || content == null) return;
//알림을 받은 topicId나 pictureID를 로컬에 저장한다.
SaveNotification(notifiedTopicId, notifiedPictureId)
//생략
}
/**
* 알림을 받은 topicId나 pictureID를 sharedPreference에 저장한다.
* 앱을 켰을때, 그동안 온 알림에 대한 정보를 알아야하기 떄문이다.
* 파라미터 예시) 사진에 댓글이 달렸을경우 notifiedTPictureId는 "0" 이 아니고,
* notifiedTopicId 는 "0" 이다.
* @param notifiedPictureId 알림의 대상이되는 사진의 id
* @param notifiedTopicId 알림의 대상이되는 토픽의 id
*/
private fun SaveNotification(notifiedTopicId: String, notifiedPictureId: String) {
if (!notifiedTopicId.equals("0") || !notifiedPictureId.equals("0")) {
val sharedPref = applicationContext.getSharedPreferences(Constants.PREF_FILE_NOTIFICATION, Context.MODE_PRIVATE)
val prefEdit = sharedPref.edit();
var notifiedPictureIDs: MutableSet<String>?
var notifiedTopicIDs: MutableSet<String>?
if (!notifiedTopicId.equals("0")) {
notifiedTopicIDs = sharedPref.getStringSet(Constants.PREF_NOTIFIED_TOPIC_ID, HashSet());
notifiedTopicIDs?.add(notifiedTopicId);
prefEdit.putStringSet(Constants.PREF_NOTIFIED_TOPIC_ID, notifiedTopicIDs);
}
if (!notifiedPictureId.equals("0")) {
notifiedPictureIDs = sharedPref.getStringSet(Constants.PREF_NOTIFIED_PICTURE_ID, HashSet());
notifiedPictureIDs?.add(notifiedPictureId);
prefEdit.putStringSet(Constants.PREF_NOTIFIED_PICTURE_ID, notifiedPictureIDs)
}
prefEdit.apply();
}
}
이런식으로 저장한 후 앱을 열었을때 꺼내썼다.
'안드로이드 앱개발' 카테고리의 다른 글
java에서 kotlin으로 마이그레이션 & 리팩토링 (1) - asyncTask를 Coroutine 으로 대체하기 (0) | 2021.04.14 |
---|---|
동영상 스플래시 스크린 띄우기 (동영상에 애니메이션 넣기) (0) | 2021.04.03 |
브로드캐스트(android studio Broadcast) (0) | 2021.01.05 |
Architecture Components(MVVM 패턴) (0) | 2020.11.20 |
안드로이드 서비스 (0) | 2020.11.20 |