Welcome! Everything is fine.

[Android] Retrofit2 이용해 FCM 댓글 푸시알림 구현하기 본문

Android

[Android] Retrofit2 이용해 FCM 댓글 푸시알림 구현하기

개발곰발 2023. 8. 19.
728x90

이전에 Firebase를 이용하여 콘솔에서 테스트 메세지를 보내는 것까지는 성공했다.

 

[Android] FCM 이용하여 테스트 메시지 보내기

📌 FCM(Firebase Cloud Messaging) 이용해 푸시 메시지 보내기 Tools - Firebase 에 들어가서 Cloud Messaging - Set up Firebase Cloud Messaging을 클릭한다. 그럼 다음과 같이 해야할 것들이 나온다. Firebase와 프로젝트는

3uomlkh.tistory.com

그러나 나는 콘솔에서 보내지 않고 앱에서 알림을 송신하고 수신하도록 만들고 싶었다. 사실 검색해도 콘솔에서 보내는 방법이 제일 많이 나와서 꽤나 삽질을 많이 했는데, Retrofit2를 사용해 댓글 알림을 구현하였다. 잊어버리지 않기 위해 정리해놓으려고 한다.(틀리거나 좀 더 좋은 방법이 있다면 댓글로 알려주세요.) 먼저 결과는 다음과 같다.

📌 결과

댓글을 달면 다음 영상과 같이 포그라운드 상태에서 알림이 온다. 또한 백그라운드 상태에서 다른 기기로 댓글을 달았을 때 알림이 오는 것을 확인했다. 지금은 테스트를 하느라 내 게시물에 내가 댓글을 달아도 알림이 오지만, 다른 사용자의 게시물에 댓글을 달았을 때만 알림이 오도록 수정해야한다.

FCM 댓글 알림

📌 FCM 알림 구현하기

Retrofit Instance

public class RetrofitInstance {
    private static Retrofit retrofit;

    public static Retrofit getClient() {
        if(retrofit == null){
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

baseUrl, Converter를 설정하는 부분이다. baseUrl은 말 그대로 baseUrl, 즉 Url에서 기본이 되는(변하지 않는) 부분을 넣으면 된다. 기억해야할 점은 baseUrl은 반드시 /로 끝나야 한다는 점이다. 나는 미리 MyKey라는 상수 데이터 클래스를 만들어 놓고 작업하였다. 여기서 baseUrl은 https://fcm.googleapis.com/ 이 된다.

POJO 클래스

알림이 올 때 나타나는 알림 제목과 내용, 메세지를 수신할 사용자의 디바이크 토큰 값을 데이터 클래스로 만든다. 먼저NotificationDataPushNotification 두 개의 파일을 만든다. NotificationData는 PushNotification에 들어가는 데이터 클래스로, titlebody, click_action으로 구성되어있다. title은 알림 제목, body는 알림 내용, click_action은 원래 알림 메세지를 누르면 넘어가는 액티비티를 넘겨주는 필드인 것 같지만, 나는 댓글이 달린 게시물의 Id를  넘겨주는데 사용했다.

public class NotificationData {
    public String title;
    public String body;
    public String click_action;
    public NotificationData(String title, String body, String click_action) {
        this.title = title;
        this.body = body;
        this.click_action = click_action;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

PushNotification은 다음과 같이 notificationData와 토큰을 담는 to로 이루어져있다. 토큰 값을 알아야 제대로 전송할 수 있다.

public class PushNotification {
    @SerializedName("notification")
    public NotificationData notificationData;

    @SerializedName("to")
    public String to;

    public PushNotification(NotificationData notificationData, String to) {
        this.notificationData = notificationData;
        this.to = to;
    }
}

NotificationAPI

다음은 API 인터페이스를 만든다. 헤더에는 서버키와 콘텐츠 타입을 넣는다. SEVER_KEYCONTENT_TYPE 역시 상수 데이터 클래스에서 가져온 것이다. 서버키는 Firebase 콘솔에서 자신의 서버키를 복사해놓고 사용하면 된다.

public interface NotificationAPI {
    @Headers({"Authorization: key=" + SERVER_KEY, "Content-Type:" + CONTENT_TYPE})
    @POST("fcm/send")
    Call<ResponseBody> sendNotification(@Body PushNotification pushNotification);
}

MyFirebaseMessagingService

Firebase 서버에서 온 메세지를 받는 부분이다. title은 알림 제목, body는 내용, click_action에는 피드 Id가 들어있다. 처음에는 여기서 피드 Id를 무작정 intent.putString()으로 보내려고 하니 자꾸 오류가 생겼다. 알고보니 푸시알림 클릭 시 일어나는 이벤트를 정의할 때는 PendingIntent를 이용한다고 한다. intent.putString()으로 보내되, 번들로 감싸 보내야한다고 한다. 이렇게 보내놓고 CommentActivity에서 번들을 받아 사용하면 된다.

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "FirebaseMsgService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, remoteMessage.getNotification().getClickAction());
            sendNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody(),
                    remoteMessage.getNotification().getClickAction());
        } else if (remoteMessage.getData().size() > 0) {
            String title = remoteMessage.getData().get("title");
            String body = remoteMessage.getData().get("body");
            String click_action = remoteMessage.getNotification().getClickAction();
            sendNotification(title, body, click_action);
        }

    }
    public void sendNotification(String title, String body, String click_action) {

        Intent intent = new Intent(this, CommentActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("POSTSDocumentId", click_action);
        intent.putExtras(bundle);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);

        String channelId = "test_channel";

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background))
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(title)
                .setContentText(body)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }

}

CommentActivity에서 통신

댓글이 달리면 댓글이 달린 게시물의 작성자 id를 변수에 저장한다. 작성자의 id를 알아야 userToken도 구할 수 있기 때문이다. 여기서 토큰은 Firestoreusers 컬렉션에 저장해둔 상태이다. 댓글 업로드 버튼을 누르면 sendCommentToFCM()을 실행하도록 한다. 여기서 나중에 댓글의 길이, 댓글 작성자의 id와 게시글 작성자 id 비교, 알림 수신 동의 등을 고려해 메서드를 실행시킬지 말지 결정해야한다.

 

sendCommentToFCM()에서는 NotificationData와 PushNotification 객체를 만들고, 댓글 내용과 피드Id, 토큰을 넣어 SendNotification()에 전송한다.

    private void sendCommentToFCM() {
        final String comment = Et_Comment.getText().toString();

        db.collection("users")
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {
                            for (QueryDocumentSnapshot documentSnapshot : task.getResult()) {
                                if (documentSnapshot.getId().equals(postPublisher)) { // user 테이블의 사용자 id가 댓글이 달린 게시글 작성자 id와 같다면
                                    token = documentSnapshot.getData().get("userToken").toString(); // 해당 사용자의 토큰을 얻는다.

                                    NotificationData data = new NotificationData("채곡채곡", "댓글이 달렸습니다 : " + comment, FeedId);
                                    pushNotification = new PushNotification(data, token);
                                    SendNotification(pushNotification);
                                }
                            }

                        } else {
                            Log.d("error", "Error getting documents", task.getException());
                        }
                    }
                });
    }

SendNotification()에서는 위에서 만들어진 pushNotification을 받아 retrofit 통신한다.

public void SendNotification(PushNotification pushNotification) {

    NotificationAPI api = RetrofitInstance.getClient().create(NotificationAPI.class);
    retrofit2.Call<ResponseBody> responseBodyCall = api.sendNotification(pushNotification);
    responseBodyCall.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            Log.d("SendNotification","성공");
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("SendNotification","실패");
        }
    });
}

이렇게 해서 테스트 해보니 정상적으로 푸시알림이 오고, 알림 클릭 시에도 해당 댓글로 잘 이동하는 것을 볼 수 있었다. 이제 추가적으로 토큰값을 업데이트 하고, 알림메세지 수신동의에 따라 노티를 조절하기만 하면 구현하고자 했던 결과에 가깝게 갈 수 있을 것이다.