24.03.02 FCM 알림 수신하기

KSang·2024년 3월 2일
0

TIL

목록 보기
77/101

지난번엔 서버에서 전체 사용자에게 알림을 보냈는데,

실제 앱을 보면 전체 알림뿐만 아닌 개인적으로 오는 알림도 나온다.

채팅을 보내면 상대방이 우선 채팅이 왔는지 알아야 할 것아닌가.

그렇기 때문에 메세지등을 보내면서 알림 또한 같이 사용자에게 보내줄 필요가있다.

사실 사용자 앱내에서 감지해서 알림을 뜨게하고 싶은데 이건 잘모르겠다.. 천천히 알아봐야겠다.

우선 사용자마다 각각 고유의 토큰을 가지고 있다.

이 토큰을 통해서 파이어 베이스에선 클라우드 메세지를 개개인한테 전달 할 수 있다.

개개인 한테 전달한다면 사용자를 구별할 uid또한 필요할 것이다.

그렇기 때문에 유저 데이터에 token을 추가해서 원하는 사용자에게 알림을 송신할 수 있게 만들어준다.

class UserRepositoryImpl @Inject constructor(
    private val firebaseAuth: FirebaseAuth,
    private val firestore: FirebaseFirestore,
    private val firebaseMessaging: FirebaseMessaging,
) : UserRepository {
   ...

    override suspend fun registerToken() = suspendCoroutine { continuation ->
        firebaseMessaging.token.addOnSuccessListener { token ->
            firebaseAuth.currentUser?.uid?.let {
                firestore.collection(DataBaseType.USER.title).document(it).update("token",token)
                    .addOnSuccessListener {
                        continuation.resume(DataResultStatus.SUCCESS)
                    }
                    .addOnFailureListener { e ->
                        continuation.resume(DataResultStatus.FAIL.apply {
                            this.message = e.message ?: "Unknown Error"
                        })
                    }
            }
        }
    }
}

유저 레포지토리의 구현부이다.

여기서 registerToken을 이용해서 현재 로그인한 사용자의 토큰을 서버에 저장하게한다.

firebaseMessaging에서 토큰을 불러오고 그걸 유저의 token필드에 추가해준다.

유저의 토큰정보를 알았으니 이제 알림을 보내는 부분을 만들어보자


우선 파이어 베이스 콘솔에서 서버키를 얻어와야한다.

서버키를 얻으면 retrofit을 사용해서 서버에 전달하면된다.

우선 retrofit의 의존성을 추가해주고

    implementation("com.squareup.retrofit2:retrofit:2.9.0")
object FcmRetrofit {
    private const val FCM_URL = "https://fcm.googleapis.com"

    private val retrofit =
        Retrofit.Builder()
            .baseUrl(FCM_URL)
            .client(provideOkHttpClient(AppInterceptor()))
            .addConverterFactory(GsonConverterFactory.create())
            .build()

    val fcmNetwork : FcmInterface =
        retrofit.create(FcmInterface::class.java)

    // Client
    private fun provideOkHttpClient(
        interceptor: AppInterceptor
    ): OkHttpClient = OkHttpClient.Builder()
        .run {
            addInterceptor(interceptor)
            build()
        }
}

retrofit을 구현해준다.

FCM_URL = "https://fcm.googleapis.com"는 파이어 베이스에서 클라우드에 보내는 푸시 메시지의 엔드 포인트다.

여기서 서버키를 헤더에 입력해 사용한다.

class AppInterceptor : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain)
            : Response = with(chain) {
        val newRequest = request().newBuilder()
            .addHeader("Authorization", "key=${BuildConfig.FCM_KEY}")
            .addHeader("Content-Type", "application/json")
            .build()
        proceed(newRequest)
    }
}

기존에 레트로핏을 구현한것들과 별 다를게 없다.

interface FcmInterface {
    @POST("fcm/send")
    suspend fun sendNotification(
        @Body notification: NotificationDTO
    ) : Response<ResponseBody>
}

이제 서버와 데이터를 주고 받으니 DTO가 필요하다.

Fcm에선 데이터 모델이 크게 2가지로 나뉜다.

지난번에 사용한 기본적인 알림 메시지

{
  "to": "디바이스 토큰 또는 주제",
  "notification": {
    "title": "알림 제목",
    "body": "알림 내용",
    "icon": "알림 아이콘",
    "click_action": "클릭 시 실행할 액티비티 또는 액션"
  }
}

클라이언트 측에서 처리하는 데이터 메시지

{
  "to": "디바이스 토큰 또는 주제",
  "data": {
    "key1": "value1",
    "key2": "value2",
    // 추가 데이터 항목...
  }
}

이 둘을 합친 복합 메시지 또한 가능하지만

우선 이번에 데이터 메시지를 사용할 것이다.

data class NotificationDTO(
    val to: String?,
    val data: NotificationData?
){
    data class NotificationData (
        val title: String?,
        val type: String?,
        val name: String?,
        val img: String?,
        val message: String?,
        val registeredDate: String?,
    )
}

to에 목표 토큰을 넣어주고 data에 넣는 데이터로 알림을 구성할 것이다.

이제 보내는일만 남았다.

우선 DTO로 보내기전 엔티티를 만들어주고

data class NotificationEntity(
    val key: String = "NF_" + LocalDateTime.now().toString(),
    val toUserToken: String? = null,
    val toUserId: String? = null,
    val type: NotifyType? = null,
    val title: String? = null,
    val name: String? = null,
    val message: String? = null,
    val thumbnail: String? = null,
    val registeredDate: String = LocalDateTime.now().convertLocalDateTime()
)
class NotifyRepositoryImpl @Inject constructor(
    private val fcmInterface: FcmInterface,
): NotifyRepository {
    override suspend fun sendNotification(notification: NotificationEntity) {
        fcmInterface.sendNotification(notification.convert())
    }

    private fun NotificationEntity.convert() = NotificationDTO(
        to = toUserToken,
        data = NotificationDTO.NotificationData(
            title = title,
            type = type?.title,
            name = name,
            message = message,
            registeredDate = registeredDate,
            img = thumbnail,
        )
    )
}

레포지토리를 통해 알림 데이터를 넘겨주면 된다.

송신부는 끝났다.

이렇게 받은 데이터는 어떻게 받으면 될까?

class FirebaseMessagingService : FirebaseMessagingService() {


    // 메세지가 수신되면 호출
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        ...
        //다른 기기에서 서버로 보낼때
        else if (remoteMessage.data.isNotEmpty()) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                remoteMessage.data["type"]?.let { type ->
                    remoteMessage.data["title"]?.let { title ->
                        remoteMessage.data["message"]?.let { message ->
                            when (NotifyType.fromTitle(type)) {
                                NotifyType.CHAT -> {
                                    remoteMessage.data["name"]?.let { name ->
                                        remoteMessage.data["img"]?.let { img ->
                                            remoteMessage.data["registeredDate"]?.let { registeredDate ->
                                                sendNotification(
                                                    title = title,
                                                    name = name,
                                                    message = message,
                                                    img = img,
                                                    channelId = "channel_$type",
                                                    registeredDate = registeredDate
                                                )
                                            }
                                        }
                                    }
                                }

                                NotifyType.INVITE ->
                                    sendNotification(
                                        title = title,
                                        channelId = "channel_$type",
                                        message = message,
                                    )

                                else -> {
                                    sendNotification(
                                        remoteMessage.notification?.title,
                                        remoteMessage.notification?.body!!
                                    )
                                }
                            }
                        }
                    }
                }
            } else {
                sendNotification(
                    remoteMessage.notification?.title,
                    remoteMessage.notification?.body!!
                )
            }
        }
    }

지난 번에 사용한 onMessageReceived에서 알림이 온걸 감지 할 것이다.

여기서 서버에서 보낸건지 다른기기에서 서버에서 보낸건지 구별해주고 받아준다.

람다를 사용하고 알림 속성마다 다른 알림을 보여주기 위해서 구별해줬는데

그냥 알림을 받아서 sendNotification에 넣었다고 보면된다.

여기서 notification builder를 이용해 알림을 띄워주면된다.

remoteView를 사용해서 커스텀했는데, 이는 다음에 적겠다.

그냥 저 데이터를 이용해서 notification을 띄우기만 하면 된다.

쓸게 너무 많다..

글쓰는것도 힘들다..

0개의 댓글