Firebase의 Auth, Realtime database만으로도 어지간한 서비스는 서버 없이도 만들 수 있었습니다.
하지만 뭔가 2% 부족했죠.
DB 데이터가 변경되었을 때 추가 작업이 이루어 져야 하는 경우가 있어 이를 구현한다고 하면 클라이언트 코드가 아주 복잡해졌습니다.
서버에서 해야할 작업을 클라이언트가 직접 해주어야 했기에 복잡성이 증가하고, 안정성?도 떨어졌죠.
(안정성을 높이기 위해 복잡성이 계속 증가하지만 과연 100% 확신할 수 있을까?…)
하지만 Firebase에 Cloud Functions 기능이 추가되면서 한단계 더 만족할만한 Serverless 서비스를 만들 수 있게 되었습니다.
[동기]
아래와 같은 상황을 가정해봅시다.
- 사용자 A가 채팅 방을 개설합니다.
- 사용자 B가 채팅 방에 입장을 요청합니다.
- A가 B의 입장을 승인/거절합니다.
- B가 입장 요청에 대한 결과를 받습니다.
DB는 Firebase의 Firestore를 사용하였습니다.
채팅 방을 개설하면 Firestore에 채팅 방 정보를 갖는 document를 생성하고
다른 사용자가 입장을 요청하면 해당 document에 입장 요청 정보를 추가합니다.
사용자 A는 document에 새로 추가된 입장 요청 정보를 확인하여 수락/거절을 하게 되는 시나리오입니다.
간단히 Firestore만 사용하여 구현이 가능하지만, 문제는 사용자 B가 채팅 방에 입장 요청했을 때 사용자 A가 이를 알기 위해서는 직접 앱을 실행하여 확인해야 하는 방법 밖에는 없습니다.
사용자 A가 앱을 실행하지 않는 상황에서도 누군가 입장 요청을 하게 되면 A에게 알려주어야 합니다.
이를 구현하기 위해서는 바로 Cloud Messaging이 필요합니다.
[소개 범위]
Cloud Messaging은 다양한 프로토콜과 다양한 전송 시나리오를 구현할 수 있게 해줍니다.
하지만 지금 여기서는 그 중 하나인 아주 일부의 예제에 대해서만 다룹니다.
https://firebase.google.com/docs/functions/use-cases?authuser=0#notify_users_when_something_interesting_happens
[구현 순서]
Client side
1. Token 등록
2. Receive Message
Server side (Clound Functios)
1. Trigger 등록
2. Send Message
본 주제는 다소 지루할 수 있어 두 번째 걸쳐 연재됩니다.
먼저 본 포스팅에서는 Client side를 다루며, 다음 포스팅에서 Server side를 다룹니다.
0. 구현
FCM에서는 어떻게 특정 사용자에게 message를 보낼 수 있을 까요?
Message를 보내기 위해서는 message를 받을 수 있는 client들의 정보(Token)를 FCM이 알고 있어야 합니다.
그렇기 때문에 message를 보내기에 앞서 각 client들은 Token을 FCM에 등록하여야 합니다.
1. Token 등록
Token을 얻기 위해 FirebaseInstanceIdService 클래스를 상속받아 onTokenRefresh() 메서드를 구현합니다.
먼저 Manifest 파일에 하기와 같이 Service를 등록합니다.
<service android:name=".service.fcm.FCMInstanceIdService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Manifest에 등록한 Service를 구현할 차례입니다.
다만 FirebaseInstanceIdService를 상속받아 구현합니다.
public class FCMInstanceIdService extends FirebaseInstanceIdService {
@Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
...
}
}
onTokenRefresh() 메서드는 하기와 같은 경우에 호출된다고 합니다.
- 앱에서 인스턴스 ID 삭제
- 새 기기에서 앱 복원
- 사용자가 앱 삭제/재설치
- 사용자가 앱 데이터 소거
제가 진행하는 프로젝트에서는 위에서 얻은 token을 Firestore의 User 정보에 저장하였습니다.
아래와 같이 User 필드에 fcmToken이 있는 것을 확인하실 수 있습니다.

Token 정보를 Firestore에 저장함으로 인해 Clound Functions에서는 손 쉽게 해당 사용자에게 message를 보낼 수 있습니다.
Token 등록 과정을 좀 더 자세히 확인하고 싶으면 아래 링크를 참고하시기 바랍니다.
https://firebase.google.com/docs/cloud-messaging/android/client?authuser=0#sample-register
2. Receive Message
Token을 등록하였으니 FCM이 정상적으로 동작한다면 token 정보를 통해 해당 사용자에게 message를 보내줄 것입니다.
FCM에서 message를 보내는 부분을 구현하기 앞서 Client에서 message를 받아 처리하기 위해 FirebaseMessagingService 를 상속받는 Service 클래스를 구현합니다.
Manifest 파일에 아래 내용을 작성합니다.
Message를 수신했을 때 Noti bar에 보여질 아이콘과 색상에 대한 정보를 <meta-data> 태그로 정의하였습니다.
수신한 message를 처리한 Service를 정의하였습니다.
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_user_add" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
...
<service android:name=".service.fcm.FCMService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
다음은 실제 서비스 구현체 입니다.
핵심은 onMessageRecuived() 메서드입니다. 인자로 들어오는 RemoteMessage에 FCM에서 보낸 message가 담겨있습니다.
아래 코드에서는 RemoteMessage에서 필요한 데이터를 꺼내 Noti를 보여주는 코드입니다.
public class FCMService extends FirebaseMessagingService {
public FCMService() {
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
if (remoteMessage.getData().size() > 0) {
sendNotification(remoteMessage.getData());
}
}
private void sendNotification(Map<String, String> messageMap) {
String recruitId = messageMap.get("recruitId");
String body = messageMap.get("body");
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_user_add)
.setContentTitle("FCM Message")
.setContentText(body)
.setAutoCancel(true)
.setSound(defaultSoundUri)
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
}
}
클라이언트 측 구현은 여기까지이며,
서버 측 구현은 다음 포스팅에서 확인할 수 있습니다.
'개발 > Android' 카테고리의 다른 글
[Android] 앱 출시 전 체크 사항 14가지 (번역) (0) | 2020.11.08 |
---|---|
[Android] Firebase FCM을 사용하여 서버 없이 push 알림 구현하기 – part 2 (0) | 2020.11.08 |
[Android] SharedPreference commit과 apply (0) | 2020.11.08 |
[Android] 화면 방향 변경 시에도 RecyclerView의 Position 유지하기 (0) | 2020.11.08 |
[Android] BroadcastReceiver를 통해 SMS 수신하기 (0) | 2020.11.08 |