#android #kotlin

FCM 설정 방법 및 클릭 시 url 열기

1jeongg 1jeongg Follow 2024년 02월 18일 · 7 mins read
Share this

PPAP 앱을 만들면서 공지사항이 새로 등록되면 서버에서 푸시 알림을 전송해줘야하기에 FCM을 도입하기로 했다.

FCM이란?

Firebase Cloud Messaging

  • 메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메시징 솔루션
  • 알림 메시지 또는 데이터 메시지 전송

01. 프로젝트 등록

1-1. 파이어베이스 프로젝트 생성

참고자료

콘솔에 들어가서 Add Project 클릭, 기타 정보 입력

1-2. FCM 설정

왼쪽창의 Engage > Messaging 클릭해서 Android 버튼 클릭

FCM

1-3. Firebase SDK 설정

📂 <project>/build.gradle.kts

id("com.google.gms.google-services") version "4.4.1" apply false

📂 <project>/<app-module>/build.gradle.kts)

plugins {
  id("com.android.application")
  id("com.google.gms.google-services")
  ...
}

dependencies {
  implementation(platform("com.google.firebase:firebase-bom:32.7.2"))
  implementation("com.google.firebase:firebase-analytics")
  implementation("com.google.firebase:firebase-messaging-ktx")
}

02. Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정

2-1. 매니페스트 설정

AndroidManifest.xml 파일에 FirebaseMessagingService를 확장하는 서비스를 추가한다.

<!-- android:name엔 FirebaseMessage를 관리하는 class의 위치를 넣어줘야함 -->
<service android:name=".data.fcm.FirebaseMessageService" 
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/icon" />

2-2. Android 13 이상에서 런타임 알림 권한 요청

Android 13 부터는 알림 권한을 받기 위해서는 반드시 사용자에게 물어봐야 한다.

// MainActivtiy.kt에 작성
private val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission(),
) { isGranted: Boolean ->
    if (!isGranted) {
        // 알림을 받을 수 없다고 사용자에게 알려줘야함
    }
}

private fun askNotificationPermission() {
    // API 레벨이 33 이상인 경우에만 가능
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            // 알림 받기 가능
        } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
            // 일반적으로 앱에서 알림을 게시하도록 권한을 부여하면 사용 설정되는 기능을 사용자에게 설명하는 UI를 표시해야 합니다. 이 UI는 확인 및 아니요 버튼과 같이 사용자가 동의하거나 거부할 수 있는 옵션을 제공해야 합니다. 사용자가 확인을 선택하면 권한을 직접 요청합니다. 사용자가 아니요를 선택하면 알림 없이 계속 진행할 수 있도록 합니다.
        } else {
            // 알림 받을 겆ㄴ지 물어봄
            requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
    }
}

나는 Composable 함수 내에서 해당 함수를 호출하기 위해 아래와 같이 작성하였다.

fun checkAndRequestLocationPermissions(
    context: Context,
    launcher: ManagedActivityResultLauncher<String, Boolean>
) {
    if (ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
        else {
            Toast.makeText(context, "설정에서 알림을 활성화시켜주세요.", Toast.LENGTH_SHORT).show()
        }
    }
}

@Composable
fun testComposable(){
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (!isGranted) {
            Toast.makeText(context, "알림이 비활성화되어 알림을 받을 수 없어요.", Toast.LENGTH_SHORT).show()
        }
    }
    Button(
        text = "알림 설정",
        onClick = { checkAndRequestLocationPermissions(context, launcher) }
    )
}

3. 메시지 수신

3-1. 메시지 수신

Firebase 알림의 동작은 수신하는 앱의 포그라운드/백그라운드 상태에 따라 달라진다.

메시지를 수신하려면 FirebaseMessagingService를 확장하는 서비스를 사용한다. 서비스에서 onMessageReceived 및 onDeletedMessages 콜백을 재정의해야 한다. 특히나 onMessageReceived는 다음 경우를 제외하고 대부분의 메시지 유형에 제공된다.

  • 앱이 백그라운드에서 실행되고 있을 때 전송된 알림 메시지: 이 경우 알림이 기기의 작업 표시줄로 전송됩니다. 사용자가 알림을 탭하면 기본적으로 앱 런처가 열립니다.

  • 알림과 데이터 페이로드가 둘 다 포함된 메시지(백그라운드에서 수신된 경우): 이 경우 알림은 기기의 작업 표시줄로 전송되고 데이터 페이로드는 런처 활동의 인텐트 부가 정보로 전송됩니다.

여기서 중요한 점은 FirebaseMessagingService의 내용은 포그라운드 상에서만 동작하고 백그라운드에서 수신된 경우 따로 설정해줘야 한다는 것이다.

앱 상태 알림 데이터 모두
포그라운드 onMessageReceived onMessageReceived onMessageReceived
백그라운드 작업 표시줄 onMessageReceived 알림: 작업 표시줄, 데이터: 인텐트 부가 정보

따라서 포그라운드, 백그라운드에서의 알림 처리를 따로 다뤄보겠다.

3-2. 포그라운드에서 알림 받았을 때 처리

앱을 사용자가 사용중일 때, 알림을 받았을 때 처리다. 이때는 그냥 MainActivity를 재실행하도록 하였다.

class FirebaseMessageService: FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        if (message.data != null){
            val intent = Intent(applicationContext, MainActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            // MainActivity 재시작
            startActivity(intent)
        }
        // body: message.notification.body
        // title: message.notification.title
        // data: message.data["key"]

        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
            // 토큰: task.result
            if (!task.isSuccessful) {
                return@OnCompleteListener
            }
        })
    }
    // FCM SDK는 앱을 처음 시작할 때(앱 제거/재설치, 앱 데이터 소거시, 새 기기에서 앱 복원 시) 클라이언트 앱 인스턴스용 등록 토큰을 생성한다.
    override fun onNewToken(token: String) {
        val key = stringPreferencesKey(DataStoreKey.FCM_TOKEN_KEY.name)
        runBlocking {
            // FCM 토큰 저장
            dataStore.edit{ it[key] = token } 
        }
        super.onNewToken(token)
    }

}

3-3. 백그라운드에서 알림 받았을 때 처리

사용자가 앱을 사용하지 않을 때는 작업 표시줄에 알림이 온다. 해당 알림을 클릭하면 url을 열도록 설정하였다.

링크를 여는 Intent를 따로 설정하여 startActivity를 통해 실행해주었다.

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        openNoticeLink()
        ...
    }

    private fun openNoticeLink() {
        try {
            if (intent?.extras != null) {
                // 알림 데이터가 있다면 링크 실행
                val link = intent.extras!!.get("link").toString()
                val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
                startActivity(browserIntent)
            }
        } catch(_: Exception){

        }
    }
}

4. 테스트

알림을 테스트해보기 위해 FCM 콘솔에서 새 캠페인을 만들어 테스트해볼 수 있다.

이때 추가 옵션에 맞춤 데이터 내용을 넣어 데이터 페이로드를 추가할 수 잇다.

나는 link - https://naver.com 을 추가해주었다.

5. 보고서

  • 전송 - 사용자에게 전송된 알림 및 데이터 메시지의 개수입니다.
  • 수신됨 - 사용자가 수신한 알림/데이터 메시지의 개수입니다(Android에서만 지원 및 Apple은 부분적으로 지원).
  • 노출 - 사용자가 수신한 알림/데이터 메시지의 개수입니다(Android에서만 지원 및 Apple은 부분적으로 지원).
  • 열림: 사용자가 연 알림 메시지의 개수입니다

보고서

이걸로 확인해볼 수 있다.

전송과 수신됨에 차이가 있는데.. 이 문제는 이제 해결해야 됨 ^0^ 신난다….



1jeongg
Written by 1jeongg Follow

I'm studying Android development by Kotlin and Spring by Java