앱 프로젝트 - 09 - 1 (푸시 알림 수신기) - FCM( Firebase 클라우드 메세징 ) - 데이터 메세지 위주로, Pending Intent

하이루·2022년 1월 28일
0
post-custom-banner

소개

  • Firebase 토큰을 확인할 수 있다.
  • 일반, 확장형, 커스텀 알림을 볼 수 있다.

Firebase 공식 홈페이지 : https://firebase.google.com/?gclid=Cj0KCQiAxc6PBhCEARIsAH8Hff1EcOYWaeDXe8FflUCiaV6rV_DYNG0DK-vpU2slGDhfBFeqZfHCxwQaAgOtEALw_wcB&gclsrc=aw.ds

활용 기술

  • Firebase Cloud Messaging
  • Notification

//////////

  1. 프로젝트 셋업하기
  2. 기본 UI 구성하기
  3. Cloud Messaging 소개
  4. Cloud Messaging 구성하기
  5. Cloud Messaging 연동하기
  6. Notification 구현하기

레이아웃 소개

<img src="https://images.velog.io/images/odesay97/post/5764c46c-3d11-45fa-ada0-aa4cb88809f7/image.png"width=300>


Firebase 프로젝트

공식문서 : https://firebase.google.com/docs/projects/learn-more

아래는 일반 권장사항에 대한 내용인데,
대충 내용을 요약하자면, 사용자 관점에서 하나의 프로젝트를 하나의 Firebase프로젝트로 할당하라는 의미이다.

예를 들어,
ios, 안드로이드 버전으로 각각 만든 하나의 쇼핑 프로젝트를 하나의 Firebase 프로젝트에 등록하는 것은 문제가 되지 않지만,
서로 다른 쇼핑 프로젝트 A와 B를 같은 Firebase 프로젝트에 등록하는 것은 문제를 발생시킬 여지가 많다는 것이다. ( 통계 집계 부분 포함 )

Firebase 프로젝트 만들기

  1. Firebase홈페이지에서 로그인

  2. Firebase 프로젝트 만들기

  3. 생성한 Firebase 프로젝트에 앱 추가 ( 이번 경우에는 Android이므로 안드로이드 )

  • 앱 등록

    Android 패키지 이름에는 안드로이드 프로젝트에서 아래의 내용을 넣어준다.
    앱 닉네임은 자유롭게
    디버그 서명 인증서는 push 메세지에는 당장 필요없으므로, 여기 설명에서는 생략한다.

  • 구성파일 다운로드

    다운 받은 파일을 안드로이드 프로젝트의 app 폴더에 넣어준다.

    ( 안드로이드 프로젝트에서 파일 보기 설정을 Android에서 Project로 바꿔야 보임 )

    해당 파일의 경우, FireBase와의 연동과 관련된 유니크한 값들이 들어있는 파일이다.
    따라서 직접 수정하지 않는 것을 추천한다.
    또한 해당 프로젝트를 github 같은 곳에 소스파일로 올리고자 할 때, 해당 파일을 빼고 올려야한다.

  • Firebase SDK 추가

    아래는 내 프로젝트에서의 예시이므로 각자 추가하란대로 추가하면 된다.

프로젝트 수준의 build.gradle에 추가

앱 수준의 build.gradle에 추가

--> 여기서 bom이란 것이 있는데,
이것은 앞으로 앱에 firebase기반의 기능들을 넣을 때 일일이 호환성 체크를 하며 지정하는 것이 번거롭기 때문에
bom에 각각의 의존성에 대한 버전들을 적어놓는 것으로 호환성에 대한 부분을 처리하는 것이다.

나는 Cloud-Massaging까지 할것이므로 앱 수준의 build.gradle에 dependencies에 추가

implementation 'com.google.firebase:firebase-messaging-ktx'


Firebase 클라우드 메세징 ( FCM )

Firebase FCM 공식문서 : https://firebase.google.com/docs/cloud-messaging

Firebase에서 제공하는 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔류션

FCM 메시지의 유형 2가지

메시지 유형에 대한 공식문서 : https://firebase.google.com/docs/cloud-messaging/concept-options

  • 알림 메시지 : 종종 '표시 메시지'로 간주됩니다. FCM SDK에서 자동으로 처리합니다.

    [ 알림 메시지의 특징 ]

    1. 구현이 쉽다.
    2. 앱이 백그라운드에 있을 때는 처리과정 없이 바로 push가 보이는 형태이기 때문에
      push를 눌러서 앱에 들어와야만 Customizing해서 Action을 할 수가 있음
      --> 여러가지 케이스에 유연하게 대응하기 어렵다.

    알림 메시지의 message는 token을 포함하고 있고,
    notification으로 title과 body에 대한 데이터를 가지고 있다.

  • 데이터 메시지 : 클라이언트 앱에서 처리합니다.

    [ 데이터 메시지의 특징 ]

    1. 앱이 백그라운드에 있든 포그라운드에 있든 상관없이 앱에서 자체적으로 처리하기 때문에
      어디서든지 데이터를 처리해줄 수 있음
      --> 여러가지 케이스에 유연하게 대응할 수 있음

    2. 알림메시지에 비해서 구현해야될 것들이 더 많음

    데이터 메시지의 message는 token을 포함하고 있고,
    data로 Nick과 body 그리고 Room에 대한 데이터를 가지고 있다.

    --> 데이터 메시지에 경우 알림 메시지보단 복잡하기 때문에 위에 메시지 유형에 대한 공식문서를 참고하는 것을 추천함

    Firebase 클라우드 메세징을 통해 테스트 메시지 전송해보기

    1. Firebase 프로젝트에 앱 프로젝트를 등록 ( 방법은 위에 적어놓았음 )

    2. 앱 수준의 build.gradle에서 firebase-messaging을 추가

      나는 Cloud-Massaging까지 할것이므로 앱 수준의 build.gradle에 dependencies에 추가

    implementation 'com.google.firebase:firebase-messaging-ktx'

    1. 테스트 메시지를 보낼 기기에 대해 앱을 통해 token을 얻음
    private val firebaseToken: TextView by lazy {
        findViewById(R.id.firebaseTokenTextView)
    }
    
    ......
          // FirebaseMessage의 Token을 가져오고, Token을 가져오는 것 자체가 Task가 실행되기 때문에 Listener를 통해 완료된 것을 받아와야함
        FirebaseMessaging.getInstance()
            .token
            .addOnCompleteListener{ task ->
                if(task.isSuccessful){
                    firebaseToken.text = task.result
                }
            }
  
  
--> 이렇게 얻은 토큰을 복사
  1. Firebase 인터페이스로 가서 Cloud Message -> send your first message 클릭
    --> 이후 제목과 텍스트 입력후 텍스트 메시지 전송 클릭
  1. FCM 등록 토큰 추가에 아까 복사한 토큰을 넣고 테스트 클릭

--> 앱을 킨 상태에서 바탕화면으로 가있어야 정상적으로 푸쉬가 온다.

등록 토큰 액세스 -> 특정 기기를 식별하기 위한 식별코드

공식문서 : https://firebase.google.com/docs/cloud-messaging/android/first-message?authuser=0

위에 테스트 메세지에서도 사용했듯이 토큰은 특정 기기를 식별하도록 도와주는 코드이다.

그런데 토큰은 특정 상황마다 새롭게 바뀌기 때문에 이것에 대해 고려해줘야 한다.

아래와 같은 상황마다 등록 토큰이 변경됨

새 토큰이 생성될 때마다 onNewToken() 메소드가 호출

-> 따라서 해당 메소드를 재정의하여 토큰이 바뀔 때마다 바뀐 토큰을 가져와서 갱신해주는 코드를 짜야함

현재 등록된 토큰을 가져오는 코드

   private val firebaseToken: TextView by lazy {
       findViewById(R.id.firebaseTokenTextView)
   }
   
   ......
         // FirebaseMessage의 Token을 가져오고, Token을 가져오는 것 자체가 Task가 실행되기 때문에 Listener를 통해 완료된 것을 받아와야함
       FirebaseMessaging.getInstance()
           .token
           .addOnCompleteListener{ task ->
               if(task.isSuccessful){
                   firebaseToken.text = task.result
               }
           }
 
 

메시지 수신

공식 문서 : https://firebase.google.com/docs/cloud-messaging/android/receive?authuser=0

Firebase를 통한 메시지를 수신하려면 FirebaseMessagingService를 확장하는 서비스를 사용해야함

--> 해당 서비스에서 onMessageReceivedonDeletedMessages 콜백을 재정의 하는 것으로 들어온 메시지를 처리할 수 있음

[ onMessageReceived로만 가지고는 처리할 수 없는 종류의 메시지 ] --> 앱이 백그라운드 상태일 때 !

  • 포그라운드 -> 앱이 켜져있고, 기기의 화면에 표시되어 있는 상태
  • 백그라운드 -> 앱이 켜져있지만, 기기의 화면에 표시되어 있지 않고, 들어가있는 상태

모든 메시지는 20초 이내에 처리되어야 함

기기에 메세지를 보내는 툴 ( firebase )

해당 주소 우측에 있음 : https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send

결과적으로 대략 이런 느낌이 된다.
--> 여기서 token의 경우 프로젝트의 앱을 실행시켜서 현재 등록된 토큰값을 가져와 넣은 것이다
( 토큰이 있어야 기기를 구분해서 거기에 보내진다. )

  • 이후 앱 프로젝트에서도 이런 메세지를 받을 준비를 해야한다.
  1. 위의 메세지 수신쪽에도 써놨듯이 FirebaseMessagingService를 상속받아
    onMessageReceived() 메소드를 재정의한다.

  2. Manifest에 해당 Service를 등록한다.

  • 메세지가 잘 갔는지, Debug를 통해 간단하게 확인할 수 있다.
  1. 먼저 메세지 수신에 대한 메소드인 onMessageReceived에 해당부분에 BreakPoint를 잡는다 ( 해당부분 클릭하면 됨 )

  2. 이후 디버그를 실행한 후 메세지를 보내면 디버그가 메세지를 잡아서 보여준다.

알림의 호환성 --> 8.0 버전 이상에서 사용하는 알림 채널 !

알림쪽은 굉장히 자주 기능이 업데이트 되고, 제약이 추가되는 영역이기 때문에
안드로이드 환경에서 알림은 호환성에 아주 민감한 기능이다.
따라서 이번 프로젝트와 같이 알림에 대해 다루는 경우 호환성을 아주 잘 챙길 필요가 있다.

알림에 대한 공식문서 : https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=ko#compatibility

현재 이 프로젝트에서는 8.0 버전부터 사용할 수 있는 **채널별 알림에 대한 영역**을 다룰 것인데, 이것 또한 호환성에 주의를 해야한다.

문서 : https://developer.android.com/training/notify-user/channels?hl=ko

8.0 이상부터는 알림을 채널에 할당하지 않으면 오류가 발생,
반대로 버전이 8.0보다 아래( 7.1 이하 )일 경우ㅡ, 알림에 채널의 개념을 사용하면 호환되지 않기 때문에 크래쉬 발생
( 채널이 8.0부터 나온 개념 )

정리하자면 8.0이상일 때만 채널을 만들어서 알림을 할당해줘야한다.

이처럼 알림을 사용할 때는 호환성에 주의하여 사용하여야 한다.

8.0 이상에서 사용하는 알림 채널

공식문서 : https://developer.android.com/training/notify-user/channels?hl=ko

이런 채널은 메세지를 발송하기 이전에 생성이 되야 한다.

코드상에 채널을 반복해서 만든다고 하더라도,
이미 채널이 만들어져 있는 상태라면 동일한 id에서 다시 생성하는 경우는 없기 때문에
앱을 시작할 때 채널을 만드는 것이 권장된다.
( 말하자면 YuYu라는 id를 가진 채널을 만든 경우에, 다시 YuYu라는 id의 채널을 만들다는 코드가 실행되어도 해당 채널이 다시 생성되진 않는다. )

        // 만약 현재 버전이 Oreo( 8버전 )버전 보다 크다면 채널을 만듬
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
        // Create the NotificationChannel
        val name = getString(R.string.channel_name)                    // 채널명
        val descriptionText = getString(R.string.channel_description)  // 채널 설명
        val importance = NotificationManager.IMPORTANCE_DEFAULT        // 채널 중요도
        
        val mChannel = NotificationChannel(CHANNEL_ID, name, importance)   
        // 채널ID, 채널명, 채널 중요도를 파라미터로 전달받아 채널을 설정
      
        mChannel.description = descriptionText
        // Register the channel with the system; you can't change the importance
        // or other notification behaviors after this
        
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
        // 설정한 채널을 생성
    }

앱을 시작할 때 채널을 만드는 것이 권장된다 하더라도
결국 채널은 메세지를 발송되기 전에만 만들어지면 된다.

예시)
만약에 다음과 같이 Emoji Party라는 이름의 채널을 만든다고 한다면


        // 만약 현재 버전이 Oreo( 8버전 )버전 보다 크다면 채널을 만듬
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.description = CHANNEL_DESCRIPTION

            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .createNotificationChannel(channel)
   
        }
    

    companion object {
        private const val CHANNEL_NAME = " Emoji Party"
        private const val CHANNEL_DESCRIPTION = "Emoji Party를 위한 채널"
        private const val CHANNEL_ID = "Channel Id"
    }

이렇게 생성한 채널은 앱의 Setting에 가면 볼 수 있다.

알림에서의 중요도 설정

공식문서 : https://developer.android.com/training/notify-user/channels?hl=ko

알림에는 각자 중요도 설정을 해줘야 한다.

  • 8.0 이상의 버전에서는 채널에 중요도 설정을 해줘야 되며, 해당 채널에 속하는 모든 알림은 채널에 할당된 중요도는 갖는다.

  • 7.1 이하의 버전에서는 알림 모두에 각각 중요도 설정을 해줘야 된다.


Firebase 데이터 메세지 구현하기 1 -> 일반형 알림

1. Firebase 프로젝트에 앱 등록

2. 앱 차원의 build.gradle에 Messaging기능 동기화

implementation 'com.google.firebase:firebase-messaging-ktx'

3. FirebaseMessagingService()를 상속받는 클래스 생성

 
package com.example.aop_part3_chapter9

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.google.firebase.messaging.ktx.remoteMessage


// 해당 내용은 Manifest에 Service로 등록해야함
class MyFirebaseMessagingService: FirebaseMessagingService() {

    override fun onNewToken(p0: String) {
        super.onNewToken(p0)
    }

    // 데이터 메시지 !!
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        // 알림 메세지가 데이터 형태로 들어옴

        // 채널을 만듬 ( 내가 만든 메소드 )
        // 8.0 이상일 경우에만 채널이 생성됨( 코드상 )
        createNotificationChannel()


        val title = message.data["title"]
        val message = message.data["message"]

        NotificationManagerCompat.from(this)
            .notify(1, createNotification(title,message))
    }


    private fun createNotificationChannel(){

        // 만약 현재 버전이 Oreo( 8버전 )버전 보다 크다면 채널을 만듬
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.description = CHANNEL_DESCRIPTION

            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .createNotificationChannel(channel)

        }
    }

    private fun createNotification(title: String?,message: String?): Notification {

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .build()

        return notificationBuilder

    }

    companion object {
        private const val CHANNEL_NAME = " Emoji Party"
        private const val CHANNEL_DESCRIPTION = "Emoji Party를 위한 채널"
        private const val CHANNEL_ID = "Channel Id"
    }

}
 
  • 위의 코드에서 이 부분이 Token이 생성되었을 때 호출되는 메소드이다.
     override fun onNewToken(p0: String) {
        super.onNewToken(p0)
    }
 

--> 여기서는 따로 재정의해주지 않았지만, 이 부분에 본래 새롭게 생성된 Token으로 다른 부분들을 수정하는 코드가 있어야한다.


  • 위의 코드에서 이 부분이 데이터 메시지를 받았을 때 호출되는 부분이다.
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        // 알림 메세지가 데이터 형태로 들어옴
        
        val title = message.data["title"]
        val message = message.data["message"]
        
        
	}

일단 해당 코드를 통해 데이터 메시지를 받으려면 아래 4번에 해당 service를 manifest로 등록한 뒤,
intent-filter로 Message 설정을 해줘야한다. ( 그래야 들어온 데이터 메시지가 해당 코드로 전달됨 )

데이터 메시지는 해당 메소드의 파라미터에 인자로 들어오며,
데이터 메시지는 Key-Value 형태를 가지고 있기 때문에 위와 같이 key값으로 value를 가져올 수 있다.


  • 위의 코드에서 이 부분이 8.0이상버전에서 Channel을 만드는 부분이다.
 

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.description = CHANNEL_DESCRIPTION

            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .createNotificationChannel(channel)

        }
 

만약 현재 버전이 Oreo( 8버전 )버전 보다 크다면 채널을 만든다는 조건문이 붙어있으며,
NotificationChannel() 메소드를 이용하여 만들고 있다.

NotificationChannel() 메소드는
첫번째 파라미터로 채널의 ID값을 받고,
두번쨰 파라미터로 채널의 이름을 받고,
세번째 파라미터로 채널의 중요도를 받는다.

( 여기서 채널의 id를 통해 해당 채널이 있는지 확인하며ㅡ,
이미 그 id의 채널이 있다면 해당 코드가 실행되더라도 생성되지 않는다. )

마지막으로 그렇게 만든 채널을 시스템 서비스로 앱에 올리고 있다.


  • 위의 코드에서 이 부분이 알림을 Build하는 부분이다.

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            

        NotificationManagerCompat.from(this)
            .notify(1, notificationBuilder.build())

NotificationCompat.Builder()을 통해 들어온 데이터를 알림으로 만들기 위한 Build 시작

NotificationCompat.Builder() 메소드는
첫번째 파라미터로 현재 위치를 받으며,
두번째 파라미터로 해당 알림이 속할 Channel의 id 값을 받는다.

이후 알림을 Building해주는데,
들어온 데이터를 바탕으로 알림을 구성하는데,
알림 아이콘(setSmallIcon()),
제목(setContentTitle()),
내용(setContentText())을 넣어주었다.

또한 setPriority()를 통해
해당 기기의 버전에 7.1 이하의 버전이라면 알림마다 중요도를 설정해야되므로 설정해주었다.
( 만약 기기 버전이 8.0 이상이면 자동으로 무시되는 부분 )

이후

NotificationManagerCompat.from(this)를 통해 현재 상속받은 FirebaseMessagingService에서 NotificationManager를 가져옴
NotificationManager에 notify메소드를 사용해서 알림을 출력
notify() 메소드는
첫번째 파라미터로 해당 notification id ( 임의로 설정 ),
두번째 파라미터로 알림으로 출력할 Notification을 넣는다.


4. 생성한 Service를 manifest에 등록하기


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.aop_part3_chapter9">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Aoppart3chapter9">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


        <service android:name=".MyFirebaseMessagingService"
           android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>


    </application>

</manifest>
  • service로 등록한 부분

        <service android:name=".MyFirebaseMessagingService"
           android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>

해당 앱에 service를 등록해준 것

여기서 service 내부에 intent-filter의 의미
앱에서 이 filter에 해당되는 이벤트에 대해서는 이 서비스가 수신하겠다는 의미이다.

그래서 intent-filter의 안에는 action태그를 사용하여 해당 intent-filter가 filtering하여 받을 이벤트를 넣는다.
( MESSAGING_EVENT를 filtering하여 받겠다는 뜻 )

즉, MESSAGING_EVENT가 발생하면 해당 service가 Message의 내용을 받는다.

그리고 service의 속성을 보면 exported라는 것이 있는데,
이것은 만약에 service가 사진 공유와 같은 외부 공유에 대한 부분이 있으면 true설정을 통해 가능하게 해주는 것이다.
이번에는 그런 부분이 없으므로 false로 설정한다.

5. 위에 테스트 메시지 전송해보기로 해당 기기에 데이터 메시지 전송해보기

Firebase 데이터 메세지 구현하기 2 -> 확장형 알림

공식문서 : https://developer.android.com/training/notify-user/expanded?hl=ko

기본적인 방식은 위에 설명한 데이터메세지 구현하기 1번과 같다

1번에 덧붙여서 속성을 추가하면 된다.

Build 부분에서 속성을 추가하는 것임

--> 아래에 2개의 예시를 넣어놨지만 공식문서에 가면 더 다양한 것들을 넣을 수 있다.
( 카톡처럼 메세지 팝업이라던지, 아니면 노래나 영상 같은 미디지 팝업이라던지 등등 )

예시 1 -> 큰 이미지 추가

예시 2 -> 큰 텍스트 박스

큰 텍스트 박스 예시 코드


......

    private fun createNotification(
        type: NotificationType,
        title: String?,
        message: String?
    ): Notification {

        // 들어온 데이터를 채널에 넣어서 알림으로 만들기 위한 Build 시작
        // 들어온 데이터를 바탕으로 알림을 구성하는데, 알림 아이콘, 제목, 내용을 넣음
        // +  해당 기기의 버전에 7.1 이하의 버전이라면 알림마다 중요도를 설정해야되므로 설정 ㅡㅡ, 만약 기기 버전이 8.0 이상이면 무시되는 부분
        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)

        when(type){
            
            // Unit ,, 그냥 나감
            NotificationType.NORMAL -> Unit
            
            NotificationType.EXPANDABLE -> {
                notificationBuilder.setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(
                            "으아아ㅏ아아아아아아아아아ㅏ아암낭ㅁ나암ㄴ암나압잘ㄷㅈ래ㅓㅗㅜ뮫ㅈㄹ뮤ㅜㅈㄷㄱ흄쟈ㅕㄷㄱ먀ㅕㄱㄷ휴먀ㅕㄷ규햐ㅕ뮺ㄷ햐ㅕㅁ쥳햠쥳혀ㅑㅁ쥬댜혀뮺댜혀ㅠㅁㅈ댜ㅕ흄쟈ㅕㄷ휴먀젿"
                        )
                )
            }        
            NotificationType.CUSTOM -> {
            }
        }
        return notificationBuilder.build()
    }
    
......

Firebase 데이터 메세지 구현하기 3 -> 커스텀 알림 ( 맞춤 알림 )

공식문서 : https://developer.android.com/training/notify-user/custom-notification?hl=ko

커스텀 알림도 기본적인 방식은 위에 설명한 데이터메세지 구현하기 1번과 같다

하지만 일반적으로 권장하지 않는 방식이긴 하다.

왜냐하면 기기마다 화면 크기가 다르기 때문에 모두 고려해줘야하기 때문이다.

또한 아래에 나와있듯이 왠만하면 배경색이나 글자색은 건들지 않고,
TextAppearance_Compat_Notification 및 TextAppearance_Compat_Notification_Title과 같이
Notification용으로 이미 만들어져 있는 style을 사용할 것을 추천한다.
( 왜냐하면 배경색이나 글자색이 기기마다 달라서 색을 잘못 설정하면 안 보일 수도 있음)

CustomView를 사용하려면 해당 알림의 레이아웃을 구현할 xml파일을 구성해야되며,
RemoteViews를 사용해야한다.

RemoteViews

RemoteViews는 앱 외부의 영역이라고 볼 수 있다.

레이아웃 파일을 RemoteViews에 넘겨주면 RemoteViews가 그 레이아웃 코드에 맞는 그림을 그려서 우리에게 돌려주는 식이다.

커스텀 알림 예제 코드

    private fun createNotification(
        type: NotificationType,
        title: String?,
        message: String?
    ): Notification {

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)

        when(type){
            
            // Unit ,, 아무것도 하지 않음
            NotificationType.NORMAL -> Unit
            
            
            // 커스텀 알림 넣는 코드
            NotificationType.CUSTOM -> {
                notificationBuilder
                    .setStyle(NotificationCompat.DecoratedCustomViewStyle() )
                    .setCustomContentView(
                        RemoteViews(
                            packageName,
                            R.layout.view_custom_notification
                        ).apply {
                            setTextViewText(R.id.title,title)
                            setTextViewText(R.id.message,message)
                        }
                    )


            }
        }

        return notificationBuilder.build()

    }
  1. setStyle()의 파라미터로 NotificationCompat.DecoratedCustomViewStyle()의 인스턴스를 넣어서 Custom Notification의 build 시작

  2. setCustomContentView()의 파라미터로 RemoteViews()의 인스턴스를 넣음
    ( 즉, Custom Notification를 구성할 레이아웃 화면을 RemoteViews가 그려주는데, 그렇게 다 그려진 데이터를 받음 )

  3. RemoteViews의 첫번째 파라미터로 패키지명을 넣고, 두번째 파라미터로 Custom Notification의 화면을 구성하는 xml 파일을 넣음
    ( 여기서 패키지명은 pachageName 키워드로 바로 불러올 수 있음 )

  4. 그렇게 하면 RemoteViews가 받은 xml을 구성하는데, RemoteViews의 setTextViewText() 메소드를 사용하여 해당 xml파일의 TextView에 접근하여 text속성의 값을 덮어 씌움

  5. 이렇게 RemoteViews가 구성한 화면을 Notification의 CustomContentView로 넣고 Notification을 build !

view_custom_notification.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        style="@style/TextAppearance.Compat.Notification.Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        tools:text="Title" />

    <TextView
        android:id="@+id/message"
        style="@style/TextAppearance.Compat.Notification"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        tools:text="Message" />

</LinearLayout>

[ view_custom_notification.xml의 구성 ]

[ 보낸 데이터 메세지 ]

[ 팝업으로 출력된 Custom notification ]

Firebase - 데이터 메세지에 리스너 추가하기 ( 클릭시 앱이 켜지도록 ) + 데이터 메세지의 내용을 앱에 전달하기

위쪽에서 데이터 메세지를 받아서 메시지 팝업을 만들어 올리는 부분까지 구현했다면,

이번에는 해당 메시지 팝업을 클릭했을 경우, 앱이 켜지면서
해당 메시지 팝업의 기반이 되었던 데이터 메시지의 내용을 앱에 전달하는 것을 해볼 것이다.
( 특정 Activity를 여는 기술이므로 Intent 사용 )

공식문서 : https://developer.android.com/training/notify-user/build-notification?hl=ko#click

위에 설명한 코드에서 메시지 팝업을 Build하는 코드에다가 아래에 표시한 코드들을 추가하면 리스너 설정이 완료된다.

위의 코드와 같이 Intent를 생성한 후, PendingIntent로 감싸서 사용할 것이다.

PendingIntent란?

PendingIntent에 대한 공식문서 : https://developer.android.com/reference/android/app/PendingIntent

일반적인 Intent와 다르게 내가 직접 Intent를 다루지 않고, 다른 누군가한테 Intent를 다룰 권한을 넘겨주는 거라고 생각하면 됨

위에 코드를 보면 setContentIntent()를 통해
PendingIntent를 notificationManager에게 넘겨줄 것인데,
내가 Intent를 사용하는 것이 아니라 notificationManager가 판단했을 때 Intent을 사용할 필요성이 있다면 사용하라고 권한을 넘겨주는 것,

결과적으로 내가 메시지 팝업을 클릭하면,
notificationManager는 Intent를 사용할 필요성이 있다고 판단하여
해당 Intent를 사용해 앱의 액티비티를 화면에 띄우는 것이다.

이번 앱에서 적용한 코드


......

    private fun createNotification(
        type: NotificationType,
        title: String?,
        message: String?
    ): Notification {


        val intent = Intent(this, MainActivity::class.java).apply { 
            putExtra("notificationType","${type.title} 타입")
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            // 기존에 해당 액티비티가 이미 있다면, 새로 생성하지 않고 그 액티비티를 재사용하여 나타냄
        }
        
        val pendingIntent = PendingIntent.getActivity(this,type.id,intent,FLAG_UPDATE_CURRENT)
    // 여기서  FLAG_UPDATE_CURRENT는 PendingIntent에서 정의된 상수임

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
        // 여기서 setAutoCancel(true)은 알림 클릭시에 알림이 실행되면서 알림이 자동으로 사라지게 하는 설정이다.( 이걸 안하면 알림을 클릭해도 사라지지 않고 남아있는다 )

        when(type){
            
            // Unit ,, 아무것도 하지 않음
            NotificationType.NORMAL -> Unit
            
            NotificationType.EXPANDABLE -> {
                notificationBuilder.setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(
                            "으아아ㅏ아아아아아아아아아ㅏ아암낭ㅁ나암ㄴ암나압잘ㄷㅈ래ㅓㅗㅜ뮫ㅈㄹ뮤ㅜㅈㄷㄱ흄쟈ㅕㄷㄱ먀ㅕㄱㄷ휴먀ㅕㄷ규햐ㅕ뮺ㄷ햐ㅕㅁ쥳햠쥳혀ㅑㅁ쥬댜혀뮺댜혀ㅠㅁㅈ댜ㅕ흄쟈ㅕㄷ휴먀젿"
                        )
                )
            }
            
            NotificationType.CUSTOM -> {
                notificationBuilder
                    .setStyle(NotificationCompat.DecoratedCustomViewStyle() )
                    .setCustomContentView(
                        RemoteViews(
                            packageName,
                            R.layout.view_custom_notification
                        ).apply {
                            setTextViewText(R.id.title,title)
                            setTextViewText(R.id.message,message)
                        }
                    )


            }
        }

        return notificationBuilder.build()

    }

......

1. 이 부분에서 Intent 설정

        val intent = Intent(this, MainActivity::class.java).apply { 
            putExtra("notificationType","${type.title} 타입")
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        }
  • 팝업을 클릭했을 경우, MainActivity가 열려야하므로 그에 대한 intent설정

  • Intent를 통해 데이터를 앱에 전달하기 위해서
    데이터 메시지를 통해 받은 데이터를 putExtra()메소드를 통해 Intent에 담음

  • Flag설정 -> Intent에 FLAG_ACTIVITY_SINGLE_TOP으로 Flag를 넣어줌
    (필수임 !)

    [ FLAG_Activity_SINGLE_TOP의 의미 ]

    일반적으로 알림이 들어오면 다음과 같이 쌓인다.
    ( 같은 식별자를 가진 알림 B가 들어오더라도 따로 쌓임 )

    intent에 FLAG_Activity_SINGLE_TOP으로 Flag를 세워놓으면, 다음과 같이 쌓이게 된다.
    ( 같은 식별자를 가진 알림 B가 들어오면 기본의 B를 새로들어온 B로 갱신시키며,
    Intent로 이동한 액티비티에서 갱신된 Intent에 대해 onNewIntent() 메소드를 실행시킨다. )

    공식문서 : https://developer.android.com/guide/components/activities/tasks-and-back-stack?hl=ko

2. 이 부분에서 PendingIntent로 1번에서 설정한 Intent를 감쌈

        val pendingIntent = PendingIntent.getActivity(this,type.id,intent,FLAG_UPDATE_CURRENT)
    // 여기서  FLAG_UPDATE_CURRENT는 PendingIntent에서 정의된 상수임
  • PendingIntent는 getActivity()메소드를 통해 구성하는데,
    해당 메소드는

    첫번째 파라미터로 현재 위치를 받고,
    두번째 파라미터로 알림에 대한 식별자를 받고 ( INT형으로 받음 ),
    세번째 파라미터로 감쌀 Intent를 받고,
    네번째 파라미터로 알림이 겹칠 때에 대한 Flag를 받는다.

  • 여기서 두번째 파라미터에 식별자란,
    해당 알림을 식별할 때 쓸 id를 의미하며, id가 같으면 같은 알림이라고 인식한다.

    --> 예를 들어 ))
    A에게서 오는 알림의 id와 B에게서 오는 알림의 id 가 같다면,
    다음과 같이 하나의 알림에 대해 서로 바꿔가면서 오게된다.

    반면에 A에게서 오는 알림의 id와 B에게서 오는 알림의 id가 다르다면,

    다음과 같이 서로 다른 알림으로 인식하여 각각 오게 된다.

  • 네번째 파라미터에 FLAG는 알림이 중복해서 왔을 경우에 어떻게 처리할지에 대한 부분이다.

    현재 위에 설정한 FLAG_UPDATE_CURRENT는
    새로운 알림이 오면 기존의 알림을 업데이트(갱신)하여 나타낸다는 의미이다.

3. 이 부분에서 생성한 PendingIntent를 notification에 세팅한다.

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
        // 여기서 setAutoCancel(true)은 알림 클릭시에 알림이 실행되면서 알림이 자동으로 사라지게 하는 설정이다.( 이걸 안하면 알림을 클릭해도 사라지지 않고 남아있는다 )
  • 다른 부분은 기존과 동일하며,
    setContentIntent() 메소드를 통해 PendingIntent를 세팅한다.

  • setAutoCancel(true)을 추가해서 메시지 팝업을 클릭했을 때
    해당 팝업이 자동으로 사라지도록 설정한다.

4. 메시지 팝업을 클릭했을 시 MainActivity에서 Intent에 담은 데이터를 받는 방법


    private val resultTextView: TextView by lazy {
        findViewById(R.id.resultTextView)
    }

......

    // 서비스 영역에서 Intent.FLAG_ACTIVITY_SINGLE_TOP의 Flag로 설정된 Intent에 대해 갱신이 일어나면 해당 메소드가 호출된다.
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)

        // 서비스 영역에서 Intent가 들어오면
        // 새로 들어온 intent로 이 파일의 Intent를 갱신해주는 코드
        setIntent(intent)
        
        resultTextView.text =( intent.getStringExtra("notificationType") ?: "앱 런처") 
        
    }

......
  • Intent에서 데이터를 받아오는 방법은 일반 Intent와 같다.

  • onNewIntent() 메소드를 오버라이드 해서 재정의 해줘야 한다.
    --> 이 메소드는 위에서 설명했듯이 FLAG_Activity_SINGLE_TOP으로 설정된 Intent가
    갱신되었을 경우에 호출되는 메소드
    이다.

    이 메소드가 호출되었을 때ㅡ,
    이 메소드의 호출을 야기한 새로 들어온 intent를 파라미터로 가져온다.
    그래서 setIntent() 메소드를 통해 기존의 intent를 새로 들어온 intent로 갱신해주는 것이다.


코드 소개

MainActivity.kt


package com.example.aop_part3_chapter9

import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.google.firebase.messaging.FirebaseMessaging

class MainActivity : AppCompatActivity() {

    private val resultTextView: TextView by lazy {
        findViewById(R.id.resultTextView)
    }

    private val firebaseToken: TextView by lazy {
        findViewById(R.id.firebaseTokenTextView)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initFirebase()
        updateResult()


    }

    // 서비스 영역에서 Intent.FLAG_ACTIVITY_SINGLE_TOP의 Flag로 설정된 Intent에 대해 갱신이 일어나면 해당 메소드가 호출된다.
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)

        // 서비스 영역에서 Intent가 들어오면
        // 새로 들어온 intent로 이 파일의 Intent를 갱신해주는 코드
        setIntent(intent)

        updateResult(true)
    }

    private fun initFirebase(){

        // FirebaseMessage의 Token을 가져오고, Token을 가져오는 것 자체가 Task가 실행되기 때문에 Listener를 통해 완료된 것을 받아와야함
        FirebaseMessaging.getInstance()
            .token
            .addOnCompleteListener{ task ->
                if(task.isSuccessful){
                    firebaseToken.text = task.result
                }
            }

    }

    @SuppressLint("SetTextI18n")
    private fun updateResult(isNewIntent: Boolean = false){
        resultTextView.text =( intent.getStringExtra("notificationType") ?: "앱 런처") +
                if(isNewIntent){
            "(으)로 갱신했습니다."
        }else{
            "(으)로 실행했습니다."
        }
    }
}

activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="결과"
        android:textSize="20sp"

        android:textStyle="bold"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/resultTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="12dp"
        android:text="결과 값 입니다."
        android:textSize="16sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Firebase 토큰"
        android:textSize="20sp"
        android:textStyle="bold"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/firebaseTokenTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="12dp"
        android:text="Loading..."
        android:textIsSelectable="true"
        android:textSize="16sp"
        tools:ignore="HardcodedText" />
    <!--    android:textIsSelectable="true" 는 해당 텍스트를 포커싱 가능하게 만들어준다ㅡㅡ, 텍스트에 나온 값을 복사하기 위해서 설정-->


</LinearLayout>

MyFirebaseMessagingService.kt --> 알림 메시지를 수신하기 위한 서비스 파일


package com.example.aop_part3_chapter9

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.google.firebase.messaging.ktx.remoteMessage


// 해당 내용은 Manifest에 Service로 등록해야함
class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(p0: String) {
        super.onNewToken(p0)
    }

    // 데이터 메시지 !!
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        // 알림 메세지가 데이터 형태로 들어옴

        // 채널을 만듬 ( 내가 만든 메소드 )
        // 8.0 이상일 경우에만 채널이 생성됨( 코드상 )
        createNotificationChannel()

        val type = message.data["type"]
            ?.let { NotificationType.valueOf(it) }
        val title = message.data["title"]
        val message = message.data["message"]

        type ?: return


        //  NotificationManagerCompat.from(this)를 통해 현재 Class에서 NotificationManager를 가져옴
        // NotificationManager에 notify메소드를 사용해서 알림을 출력
        //  notify() 메소드는 첫번째 파라미터로 해당 notification id ( 임의로 설정 ), 두번째 파라미터로 알림으로 출력할 Notification을 넣는다.
        // Builder를 통해 설정한 알림을 build()메소드를 통해 생성함
        NotificationManagerCompat.from(this)
            .notify(type.id, createNotification(type ,title, message))
    }


    private fun createNotificationChannel() {

        // 만약 현재 버전이 Oreo( 8버전 )버전 보다 크다면 채널을 만듬
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.description = CHANNEL_DESCRIPTION

            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .createNotificationChannel(channel)

        }
    }

    private fun createNotification(
        type: NotificationType,
        title: String?,
        message: String?
    ): Notification {

       // 해당 서비스 차원에서 데이터 메세지를 통해 데이터가 들어오는데, 그렇게 들어온 데이터를 MainActivity에 전달하기 위한 intent
        val intent = Intent(this, MainActivity::class.java).apply { 
            putExtra("notificationType","${type.title} 타입")
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            // 기존에 해당 액티비티가 이미 있다면, 새로 생성하지 않고 그 액티비티를 재사용하여 나타냄
        }

        // 위의 Intent를 PendingIntent로 감싸서 notification에 설정
        val pendingIntent = PendingIntent.getActivity(this,type.id,intent,FLAG_UPDATE_CURRENT)
        // 여기서  FLAG_UPDATE_CURRENT는 PendingIntent에서 정의된 상수임
        

        // pendingIntent는 알림이 들어올 때마다 생성될 것인데, 만약에 별다른 설정을 해주지 않고, 그냥 pendingIntent만 생성해줬다면 같은 pendingIntent만 생성되었을 것이다.
        // 그런데 위에 FLag를 SINGLE_TOP으로 해줬기 때문에 이 경우 결국 마지막에 남은 pendingIntent만이 유효하게 된다. 
        // (예를 들어, 일반 알림, 커스텀 알림, 확장알림을 번갈아 보냈을 경우, 마지막에 커스텀 알림이 왔다면 위에 putExtra() 코드에서 notificationType은 Custom타입으로 설정되어 그것만이 적용되었을 것임)
        // 이를 방지하기 위해 PendingIntent의 두번쨰 파라미터로 Int형의 식별자를 사용하며, 이를 통해 다른 알림에 대한 PendingIntent라고 구분한다.
        // ㅡㅡ, 이번 앱에서는 알림의 종류가 Normal, Expandable, custom으로 나뉘며, 그것은 type의 id값으로 구분될 수 있으므로 그것을 식별자로 사용했다.(type.id)
        // 또한 PendingIntent의 네번째 파라미터로  FLAG_UPDATE_CURRENT를 줘서 같은 종류안에서는 PendingIntent가 업데이트되며, 다른 종류끼리는 서로 구분될 것이다. (Normal, Expandable, custom)

        // 만약에 PendingIntent.getActivity(this,0,intent,0) 이었다면, 타입별로 구분되지 않으므로 마지막에 들어온 PendingIntent만이 남을 것이고,
        // UPDATE_CURRENT가 아니므로 업데이트가 되지 않을 것이다.



        // 들어온 데이터를 채널에 넣어서 알림으로 만들기 위한 Build 시작
        // 들어온 데이터를 바탕으로 알림을 구성하는데, 알림 아이콘, 제목, 내용을 넣음
        // +  해당 기기의 버전에 7.1 이하의 버전이라면 알림마다 중요도를 설정해야되므로 설정 ㅡㅡ, 만약 기기 버전이 8.0 이상이면 무시되는 부분
        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_baseline_circle_notifications_24)
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
        // 여기서 setAutoCancel(true)은 알림 클릭시에 알림이 실행되면서 알림이 자동으로 사라지게 하는 설정이다.( 이걸 안하면 알림을 클릭해도 사라지지 않고 남아있는다 )

        when(type){
            
            // Unit ,, 아무것도 하지 않음
            NotificationType.NORMAL -> Unit
            
            NotificationType.EXPANDABLE -> {
                notificationBuilder.setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(
                            "으아아ㅏ아아아아아아아아아ㅏ아암낭ㅁ나암ㄴ암나압잘ㄷㅈ래ㅓㅗㅜ뮫ㅈㄹ뮤ㅜㅈㄷㄱ흄쟈ㅕㄷㄱ먀ㅕㄱㄷ휴먀ㅕㄷ규햐ㅕ뮺ㄷ햐ㅕㅁ쥳햠쥳혀ㅑㅁ쥬댜혀뮺댜혀ㅠㅁㅈ댜ㅕ흄쟈ㅕㄷ휴먀젿"
                        )
                )
            }
            
            NotificationType.CUSTOM -> {
                notificationBuilder
                    .setStyle(NotificationCompat.DecoratedCustomViewStyle() )
                    .setCustomContentView(
                        RemoteViews(
                            packageName,
                            R.layout.view_custom_notification
                        ).apply {
                            setTextViewText(R.id.title,title)
                            setTextViewText(R.id.message,message)
                        }
                    )
            }
        }

        return notificationBuilder.build()

    }

    companion object {
        private const val CHANNEL_NAME = " Emoji Party"
        private const val CHANNEL_DESCRIPTION = "Emoji Party를 위한 채널"
        private const val CHANNEL_ID = "Channel Id"
    }
}

NotificationType.kt


package com.example.aop_part3_chapter9

enum class NotificationType (val title: String, val id: Int){

    NORMAL("일반 알림",0),
    EXPANDABLE("확장형 알림",1),
    CUSTOM("커스텀 알림", 3)

}

view_custom_notification.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        style="@style/TextAppearance.Compat.Notification.Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        tools:text="Title" />

    <TextView
        android:id="@+id/message"
        style="@style/TextAppearance.Compat.Notification"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        tools:text="Message" />

</LinearLayout>

AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.aop_part3_chapter9">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Aoppart3chapter9">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

<!--         해당 앱에 service를 등록해준 것-->
<!--        여기서 service 내부에 intent-filter는 앱에서 이 filter에 해당되는 이벤트에 대해서는 이 서비스가 수신하겠다는 의미이다.-->
<!--        그래서 intent-filter의 안에는 action태그를 사용하여 해당 intent-filter가 filtering하여 받을 이벤트를 넣는다.-->
<!--        ( 아래의 경우 MESSAGING_EVENT를 filtering하여 받겠다는 뜻 ), MESSAGING_EVENT가 발생하면 해당 service가 받는다.-->
<!--        그리고 service의 속성을 보면 exported라는 것이 있는데, 이것은 만약에 service가 사진 공유와 같은 외부 공유에 대한 부분이 있으면 true설정을 통해 가능하게 해주는 것인데-->
<!--        이번에는 그런 부분이 없으므로 false로 설정한다.-->
        <service android:name=".MyFirebaseMessagingService"
           android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>


    </application>

</manifest>

profile
ㅎㅎ
post-custom-banner

0개의 댓글