Android 알림 사용

이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 과정 Codelab은 Kotlin Codelab의 고급 Android Codelab 방문 페이지에 나열되어 있습니다.

소개

알림은 앱의 UI 외부에 사용자에게 표시되는 메시지입니다. 기기가 잠금 해제되어 있거나 보안 설정에 따라 기기가 잠겼을 때 잠금 화면에 알림이 표시됩니다.

일반적인 알림은 제목, 설명, 아이콘으로 구성됩니다. 알림에는 클릭 가능한 작업, 빠른 답장, 확장형 콘텐츠, 이미지도 포함될 수 있습니다.

알림은 시기적절하게 자료를 전달할 수 있으며 사용자가 답장이나 알람을 다시 듣는 등의 빠른 작업을 할 수 있는 버튼을 포함할 수 있습니다. 알림을 클릭하면 사용자는 알림 콘텐츠와 관련된 앱의 뷰를 볼 수 있습니다.

알림은 사용자에게 중요한 작업을 알리거나, 무슨 일이 발생했는지, 앱이 백그라운드에 있는 동안 필요한 중요한 정보를 즉시 전달하는 데 유용합니다. 알림은 최소한으로 사용합니다. 사용자를 존중할 뿐만 아니라 앱 알림이 해당 사용자의 관심을 끌 수도 있습니다.

이 Codelab에서는 Android 앱에서 알림을 만들고 사용하는 방법을 알아봅니다.

기본 요건

다음을 잘 알고 있어야 합니다.

  • Kotlin으로 Android 앱을 크레이트하는 방법 특히 Android SDK를 사용하는 것이 좋습니다.
  • 아키텍처 구성요소와 데이터 결합을 사용하여 앱을 설계하는 방법
  • BroadcastReceivers에 관한 기본 이해
  • AlarmManager에 관한 기본 이해

학습할 내용

  • 알림을 만들고, 스타일을 지정하고, 알림을 보내는 방법
  • 알림을 취소하는 방법
  • 알림 채널을 만드는 방법
  • 알림에 빠른 작업을 추가하는 방법
  • 앱 아이콘에 알림 배지를 표시하는 방법

실습할 내용

  • 시작 앱에 알림을 추가합니다.
  • 이전에 보낸 알림을 취소합니다.
  • 다양한 유형의 알림을 위한 채널을 만듭니다.
  • 시작 앱에서 알림을 맞춤설정합니다.
  • 빠른 작업을 추가하여 알림을 대화형으로 만드세요.
  • 알림 배지를 사용 중지합니다.

달걀을 요리하는 것은 간단한 작업이지만, 시간을 것입니다. 이 Codelab에서는 달걀 타이머 앱을 작업하여 향후 달걀과 마찬가지로 완벽하게 만들 것입니다. 먼저 다양한 달걀 스타일에 따라 요리 시간을 설정할 수 있는 작동하는 달걀 타이머 앱으로 시작합니다. 선택한 시간 간격부터 타이머가 카운트다운하고 달걀이 준비되면 토스트 메시지를 표시합니다.

이 어댑터는 기능적으로 보일 수도 있지만 완벽하지는 않으며 실제로 사용자 친화적이지 않습니다. 먼저 토스트 메시지는 잠시 동안만 표시되므로 놓치기 쉽습니다. 또한 앱이 포그라운드에 없거나 기기가 잠겨 있는 경우 토스트 메시지가 사라지면 타이머의 시각적 표시기가 없습니다.

이상적인 시간으로 계란 타이머는 시간이 되면 사용자에게 알림을 전달합니다. 사용자는 달걀이 즉시 준비되었음을 알고 있어야 합니다. 그렇지 않으면 난자가 과소 첨가될 것입니다. 알림은 시각적으로 설정되어 소리를 포함할 수 있으며 기기를 진동으로 변경하여 사용자의 주의를 끌 수 있습니다. 이렇게 하면 완벽한 계란과 만족스러운 행복을 누릴 수 있습니다.

샘플 앱을 가져오려면 다음 중 하나를 진행합니다.

GitHub에서 저장소를 클론하고 시작 분기로 전환합니다.

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications


또는 저장소를 ZIP 파일로 다운로드하여 압축을 푼 후 Android 스튜디오에서 열 수 있습니다.

ZIP 파일 다운로드

  1. Android 스튜디오에서 앱을 열고 실행합니다.

달걀 이미지와 달걀을 요리하는 사전 정의된 시간 목록이 포함된 드롭다운 메뉴가 표시됩니다. Soft Boiled 드롭다운 메뉴의 삼각형을 클릭합니다. 목록의 첫 번째 옵션이 테스트 목적으로 제공되며 알람을 10초로만 설정합니다. 목록 옆에는 에그 타이머를 시작하는 스위치가 있습니다. 이 스위치를 사용하여 언제든지 달걀 타이머를 시작하거나 중지할 수 있습니다. 시작 코드가 완전히 작동하면 에그 타이머를 설정하고 0으로 카운트다운하는 것을 확인할 수 있습니다. 타이머가 종료되면 아래와 같이 토스트 메시지가 표시됩니다.

  1. 소스 코드를 검사합니다. 시작 앱은 MainActivity라는 단일 활동으로 구성됩니다. 이름이 receiver, ui, util인 하위 패키지가 3개 있습니다.

  • /receiver - receiver 패키지에는 이름이 AlarmReceiverSnoozeReceiver인 broadcast receiver가 두 개 포함됩니다. 사용자 정의 타이머가 실행되면 알림을 보내기 위해 AlarmManagerAlarmReceiver를 트리거합니다. SnoozeReceiver에서 사용자가 클릭하여 알림을 일시중지하는 작업을 처리합니다.
  • /ui: 앱의 UI 부분에 포함된 EggTimerFragment를 포함합니다. EggTimerViewModel는 타이머를 시작 및 취소하고 기타 수명 주기 관련 앱 작업을 담당합니다.
  • /util: 이 패키지에 두 개의 파일이 있습니다. BindingUtils.kt에는 앱 UI와 ViewModel 간의 데이터 결합을 사용 설정하는 결합 어댑터가 있습니다. NotificationUtils.kt에는 NotificationManager의 확장 메서드가 있습니다.

알림은 사용자의 관심을 앱을 유도하기에 좋은 방법입니다. 앱이 포그라운드에서 실행되거나 포그라운드에서 실행되고 있지 않을 때 알림에는 소리와 진동이 포함될 수 있는 화면 상단에 팝업 창이 표시됩니다. 알림을 만들려면 알림 빌더를 사용하고 제목 텍스트, 콘텐츠 텍스트, 아이콘을 제공해야 합니다. 빌더에 시스템 서비스인 NotificationManager에 필요한 모든 필드가 있으면 이 콘텐츠를 알림으로 표시할 수 있습니다. NotificationManager에서 알림을 보내고 콘텐츠를 업데이트하고 알림을 취소합니다. 다음 단계에서는 NotificationManager에 확장 메서드를 추가합니다. 이렇게 하면 NotificationManager를 사용해야 할 때마다 이러한 확장 함수를 사용하여 필요한 기능을 구현할 수 있습니다.

1단계: 기본 알림 만들기

이 작업에서는 새 알림을 만들고, 사용자에게 메시지를 설정하고, 알림을 전송합니다.

  1. NotificationUtils.kt 클래스를 열고 TODO: Step 1.1를 찾습니다. 이 Codelab 및 앱 코드에서 일치하는 할 일을 확인할 수 있습니다.
  2. 지정된 sendNotification() 함수를 확인합니다. 이 확장 함수를 NotificationManager(으)로 확장하여 알림을 보냅니다.
//NotificationUtils.kt
// TODO: Step 1.1 extension function to send messages (GIVEN)
/**
 * Builds and delivers a notification.
 *
 * @param messageBody, notification text.
 * @param context, activity context.
 */
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
  1. 알림 빌더의 인스턴스를 가져와 앱 컨텍스트와 채널 ID를 전달합니다. 채널 ID는 채널의 문자열 값입니다.

알림 채널은 알림을 그룹화하는 방법입니다. 개발자와 사용자는 비슷한 유형의 알림을 그룹화하여 채널의 모든 알림을 관리할 수 있습니다. 채널을 만든 후 이를 사용해 수많은 알림을 전달할 수 있습니다.

//NotificationUtils.kt
// TODO: Step 1.2 get an instance of NotificationCompat.Builder
val builder = NotificationCompat.Builder(
        applicationContext,
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 알림 아이콘을 설정하여 앱, 제목, 사용자에게 제공할 메시지의 콘텐츠 텍스트를 표시합니다. Codelab에서 알림을 맞춤설정할 수 있는 더 많은 옵션이 표시되지만, 이는 알림을 전송하기 위해 설정해야 하는 최소 데이터 양입니다.
//NotificationUtils.kt
   // TODO: Step 1.3 set title, text and icon to builder
   .setSmallIcon(R.drawable.cooked_egg)
   .setContentTitle(applicationContext.getString(R.string.notification_title))
   .setContentText(messageBody)
  1. 그런 다음 알림의 고유 ID와 빌더의 Notification 객체를 사용하여 notify()를 호출해야 합니다.

이 ID는 현재 알림 인스턴스를 나타내며, 이 알림을 업데이트하거나 취소하는 데 필요합니다. 앱에는 한 번에 하나의 활성 알림만 있으므로 모든 알림에 동일한 ID를 사용할 수 있습니다. 이미 NotificationUtils.ktNOTIFICATION_ID라는 이름의 이 상수를 제공했습니다. 동일한 클래스의 확장 함수에서 호출을 실행하고 있으므로 notify()를 직접 호출할 수 있습니다.

//NotificationUtils.kt
   // TODO: Step 1.4 call notify to send the notification
    // Deliver the notification
    notify(NOTIFICATION_ID, builder.build())
  1. ui/EggTimerViewModel.kt를 열고 startTimer() 함수를 찾습니다. 이 함수는 사용자가 계란 타이머를 사용 설정할 때 선택한 시간 간격으로 알람을 만듭니다.
  2. 사용자가 타이머를 시작하면 이 함수에서 알림을 트리거합니다. 이전에 구현한 sendNotification() 함수를 호출하려면 NotificationManager 인스턴스가 필요합니다. NotificationManager는 추가한 확장 함수를 포함하여 알림 API에 노출된 모든 함수를 제공하는 시스템 서비스입니다. 언제든지 알림을 전송, 취소, 업데이트하려는 경우 시스템에 NotificationManager 인스턴스를 요청해야 합니다. 알림 메시지와 컨텍스트를 사용하여 sendNotification()| 함수를 호출합니다.
// EggTimerViewModel.kt
// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification

val notificationManager = ContextCompat.getSystemService(
    app, 
    NotificationManager::class.java
) as NotificationManager
                notificationManager.sendNotification(app.getString(R.string.timer_running), app)

거의 다 했어요. 하지만 지금 앱을 실행하고 타이머를 설정해도 알림이 표시되지 않습니다.

  1. logcat을 열고 "No Channel found"를 검색합니다. egg_channel가 존재하지 않는다는 오류 메시지가 표시됩니다. 다음 단계에서는 알림 채널에 대해 자세히 알아보고 문제를 해결합니다.

2단계: 알림 채널

API 수준 26부터는 모든 알림을 채널에 할당해야 합니다. 앱 런처 아이콘을 길게 탭한 후 앱 정보를 선택하고 알림을 탭하시면 앱과 연결된 알림 채널 목록이 표시됩니다. 현재 앱에서 채널을 만들지 않았으므로 목록이 비어 있습니다.

채널은 '유형'을 나타냅니다. 예를 들어 달걀이 요리되면 계란 타이머가 알림을 전송하고 다른 채널을 사용하여 아침 식사와 계란이 있음을 알려주는 일일 알림을 보낼 수도 있습니다. 채널의 모든 알림은 함께 그룹화되며 사용자는 전체 채널의 알림 설정을 구성할 수 있습니다. 이를 통해 사용자는 관심 있는 알림의 종류에 따라 알림 설정을 맞춤설정할 수 있습니다. 예를 들어, 사용자가 아침 알림을 사용 중지해도 타이머에 알림이 표시되도록 선택할 수 있습니다.

개발자는 초기 설정, 중요도, 동작을 설정하여 채널의 모든 알림에 적용할 수 있습니다. 초기 설정을 마치면 사용자가 이 설정을 재정의할 수 있습니다.

1.1단계에서 egg_notification_channel_id을 알림 채널로 사용했으므로 이제 이 채널의 알림 설정과 동작을 실제로 맞춤설정하고 맞춤설정해야 합니다.

  1. EggTimerFragment.kt를 열고 createChannel() 함수를 찾습니다.
  2. 고유 채널 ID를 NotificationChannel의 생성자에 전달합니다.
  3. 알림 채널 이름을 전달하면 사용자가 설정 화면에서도 볼 수 있습니다.
  4. 마지막 매개변수로 알림 채널의 중요도 수준을 전달합니다. 중요도 수준은 이 Codelab의 뒷부분에서 다루므로 NotificationManager.IMPORTANCE_LOW를 사용할 수 있습니다.
  5. notificationChannel 객체에서 enableLights를 true로 설정합니다. 이 설정을 사용하면 알림이 표시될 때 조명이 켜집니다.
  6. 알림이 표시될 때 빨간색 표시등을 표시하기 위해 notificationChannel 객체에서 lightColor를 빨간색으로 설정합니다.
  7. 진동을 사용 설정하려면 notificationChannel 객체에서 enableVibration를 true로 설정합니다.
  8. notificationChannel 객체에서 채널 설명을 ‘Time for breakfast'로 설정합니다.
  9. getSystemService()을 호출하여 NotificationManager 인스턴스를 가져옵니다.
  10. NotificationManager에서 createNotificationChannel()를 호출하고 이전 단계에서 만든 notificationChannel 객체를 전달합니다.
//EggTimerFragment.kt
private fun createChannel(channelId: String, channelName: String) {
    // TODO: Step 1.6 START create a channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationChannel = NotificationChannel(
            channelId,
            channelName,
            // TODO: Step 2.4 change importance
            NotificationManager.IMPORTANCE_LOW
        )
        // TODO: Step 2.6 disable badges for this channel

        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.RED
        notificationChannel.enableVibration(true)
        notificationChannel.description = "Time for breakfast"

        val notificationManager = requireActivity().getSystemService(
            NotificationManager::class.java
        )
        notificationManager.createNotificationChannel(notificationChannel)
    }
    // TODO: Step 1.6 END create channel
}
  1. 채널을 만들려면 방금 작성한 createChannel() 함수를 호출해야 합니다 (1.7단계). 이 함수는 채널 ID와 채널 이름이라는 두 매개변수를 사용합니다. 프로젝트에 이미 제공된 문자열 리소스에서 채널 ID와 채널 이름을 조회해야 합니다.
// EggTimerFragment.kt
    // TODO: Step 1.7 call createChannel
    createChannel(
          getString(R.string.egg_notification_channel_id),
          getString(R.string.egg_notification_channel_name)
    )
  1. 채널 ID를 알림 빌더에 전달해야 합니다. 1.2단계에서는 이미 이렇게 했습니다. 채널 ID로 잘못된 값을 설정하면 알림이 실패합니다. NotificationUtils.kt을 열어 이전에 설정한 채널 ID가 올바른지 확인합니다.
// NotificationUtils.kt
val builder = NotificationCompat.Builder(
        applicationContext,
       // TODO: Step 1.8 verify the notification channel name
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 앱을 실행하면 타이머를 시작할 때마다 앱에서 알림을 보냅니다.
  2. 상태 표시줄을 가져와 이전 단계에서 설정한 대로 알림 제목, 내용, 아이콘이 있는지 확인합니다.
  3. 새로 만든 채널을 확인하려면 앱을 닫고 앱 아이콘을 찾습니다. 앱 아이콘을 길게 탭하고 앱 정보를 선택합니다.

  1. 설정 목록에서 알림을 선택합니다. 알림 표시 설정 바로 아래에 계란이라는 새 채널이 표시됩니다.

이제 앱을 실행하면 알림이 표시됩니다. 앱 개발자 및 사용자 모두 이 채널에서 전송되는 모든 알림의 설정과 동작을 맞춤설정할 수 있습니다. 축하합니다. 알림을 만들었습니다.

3단계: 앱에 알림 추가

지금까지는 알림 API의 기본 사용법을 소개했지만, 타이머를 시작한 직후 알림을 전송하는 것은 타당하지 않습니다. 사용자는 달걀이 준비되면 알림을 받고 싶어할 것입니다. Codelab의 이 부분에서 이 문제를 해결하고 토스트 메시지를 알림으로 변경합니다.

이미 알림을 전송했고 사용자에게 어떻게 표시되는지 관찰했지만, 이는 뛰어난 알림을 만들기 위한 첫 단계에 불과합니다. 이 단계에서는 더 적절한 시간에 알림을 받도록 변경합니다.

앱에서 AlarmManager를 사용하여 알람을 설정합니다. AlarmManager와 관련된 코드는 이미 시작 코드에 포함되어 있으며 토스트 메시지를 표시하는 데 사용됩니다. AlarmManager는 원하는 시간 선택을 추적하고 시간이 다 되면 AlarmReceiver.ktonReceive() 함수를 트리거합니다. AlarmReceiver.kt을 열고 onReceive()로 이동하면 에그 타이머를 설정할 때마다 표시되는 토스트 메시지가 표시됩니다.

  1. NotificationManager의 인스턴스인 AlarmReceiver.kt를 열고 메시지 텍스트 및 컨텍스트 매개변수를 사용하여 sendNotification() 함수를 호출합니다.
// AlarmReceiver.kt
   // TODO: Step 1.9 add call to sendNotification
   val notificationManager = ContextCompat.getSystemService(
       context, 
       NotificationManager::class.java
   ) as NotificationManager
             
   notificationManager.sendNotification(
       context.getText(R.string.eggs_ready).toString(), 
       context
   )
  1. 원하는 경우 타이머가 끝나면 앱에서 알림을 전송하므로 토스트 메시지를 삭제합니다.
// AlarmReceiver.kt
     // TODO: Step 1.10 [Optional] remove toast
//   Toast.makeText(
//       context, 
//       context.getText(R.string.eggs_ready),
//       Toast.LENGTH_SHORT
//   ).show()
  1. 앱을 실행합니다 . 타이머를 시작할 때마다 및 타이머가 끝날 때마다 알림이 표시됩니다.

이상하지 않습니다. 사용자에게 너무 많은 알림을 보내지 않는 것이 좋습니다. 사용자가 타이머를 시작할 때 전송되는 첫 번째 알림을 삭제할 수 있습니다.

  1. EggTimerFragment.kt을 열고 1.5단계의 알림 코드를 삭제합니다.
// EggTimeViewModel.kt

// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification
// val notificationManager = ContextCompat.getSystemService(
//      app,
//      NotificationManager::class.java
// ) as NotificationManager
// notificationManager.sendNotification(app.getString(R.string.eggs_ready), app)
  1. 앱을 다시 실행합니다.
  2. 타이머를 설정하고 백그라운드에서 시작한 후 시간이 끝날 때까지 기다리세요. 알림이 표시됩니다. 훨씬 더 유용한 알림입니다.

4단계: 콘텐츠 인텐트 추가하기

  1. 아직 앱이 실행되고 있지 않다면 앱을 다시 실행합니다.
  2. 알림을 클릭합니다. 아무 일도 일어나지 않습니다.

알림을 표시하고 사용자에게 알리는 것이 훌륭하지만, 사용자가 알림을 클릭하면 해당 앱으로 돌아가기를 기대합니다. Codelab의 이 부분에서 인텐트를 추가하여 사용자를 타이머 화면으로 다시 이동합니다.

Intent는 다른 앱 구성요소에서 작업을 요청하는 데 사용할 수 있는 메시지 객체입니다. 인텐트는 활동, 서비스 시작 또는 방송 제공에 사용할 수 있습니다. 이 경우, 이 인텐트를 사용하면 사용자가 알림을 탭할 때 MainActivity를 열도록 시스템에 알릴 수 있습니다. 앱은 단일 뷰로만 구성되므로 여기에 많은 옵션이 없습니다. 하지만 더 큰 앱에서는 사용자가 알림과 상호작용할 때 합리적으로 사용자를 안내하는 화면으로 알림을 표시해야 합니다.

  1. NotificationUtils.kt을 열고 sendNotification() 확장 함수를 찾습니다.
  2. applicationContext 및 실행할 활동 MainActivity::class.java를 사용하여 Intent를 만듭니다.
// NotificationUtils.kt

fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
    // Create the content intent for the notification, which launches
    // this activity
   // TODO: Step 1.11 create intent
    val contentIntent = Intent(applicationContext, MainActivity::class.java)

인텐트를 만들었지만 알림이 앱 외부에 표시됩니다. 앱 외부에서 인텐트가 작동하도록 하려면 새 PendingIntent를 만들어야 합니다.

PendingIntent는 다른 애플리케이션 또는 시스템에 애플리케이션을 대신하여 작업을 수행할 수 있는 권한을 부여합니다. PendingIntent 자체는 시스템에서 이를 검색하는 데 사용된 원래 데이터를 설명하며 유지관리되는 토큰의 참조입니다. 즉, 자체 애플리케이션 프로세스가 종료되더라도 PendingIntent 자체는 제공된 다른 프로세스에서 계속 사용할 수 있습니다. 이 경우 시스템은 타이머 앱의 실행 여부와 관계없이 시스템에서 대신 앱을 열기 위해 대기 중인 인텐트를 사용합니다.

  1. applicationContext, NOTIFICATION_ID, 이전 단계에서 만든 contentIntent, PendingIntent 플래그를 사용하여 PendingIntent을 만듭니다. PendingIntent 플래그는 새 PendingIntent를 만들거나 기존 항목을 사용하는 옵션을 지정합니다. 기존 알림이 있는 경우 새 알림을 만들지 않으려는 경우 PendingIntent.FLAG_UPDATE_CURRENT를 플래그로 설정해야 합니다. 이렇게 하면 제공 중인 인텐트와 연결된 현재 PendingIntent를 수정합니다.
// NotificationUtils.kt
   // TODO: Step 1.12 create PendingIntent
    val contentPendingIntent = PendingIntent.getActivity(
        applicationContext, 
        NOTIFICATION_ID,
        contentIntent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
  1. PendingIntent를 알림에 전달합니다. NotificationBuilder에서 setContentIntent()를 호출하면 됩니다. 이제 알림을 클릭하면 PendingIntent가 트리거되어 MainActivity가 열립니다.
  2. 또한 setAutoCancel()true로 설정하면 사용자가 알림을 탭하면 앱으로 이동했을 때 알림이 닫힙니다.
// NotificationUtils.kt
    // TODO: Step 1.13 set content intent
    .setContentIntent(contentPendingIntent)
    .setAutoCancel(true)
  1. 앱을 다시 실행합니다.
  2. 타이머를 설정하고 앱을 백그라운드로 설정한 후 알림이 표시될 때까지 기다립니다.
  3. 알림이 표시되면 상태 표시줄을 아래로 당겨 알림을 클릭하고 앱이 포그라운드로 이어지는 방식을 확인합니다.

5단계: 알림 취소하기

알림과 함께 작동하는 달걀 타이머가 있지만 사소한 문제가 있습니다. 타이머를 설정하고 알림을 받은 후 타이머를 다시 설정하면 새 타이머가 실행되는 동안 이전 알림이 상태 표시줄에 유지됩니다. 앱이 백그라운드에 있다면 혼란스러울 수 있으며 이로 인해 달걀이 완만해질 수 있습니다.

이 문제를 해결하려면 새 타이머를 시작할 때 이전 알림을 삭제해야 합니다. 먼저 NotificationUtils.kt에 다른 확장 함수를 만듭니다. NotificationManager에는 cancelAll()라는 모든 활성 알림을 취소하는 API가 있습니다.

  1. NotificationsUtil.kt를 엽니다.
  2. NotificationManagercancelAll()를 호출하는 확장 함수를 추가합니다.
// NotificationUtils.kt

// TODO: Step 1.14 Cancel all notifications
/**
 * Cancels all notifications.
 *
 */
fun NotificationManager.cancelNotifications() {
    cancelAll()
}
  1. EggTimerViewModel.kt를 열고 startTimer() 함수로 이동합니다.
  2. startTimer() 내부에 있는 시스템에서 NotificationManager 인스턴스를 가져와 cancelNotifications()를 호출합니다.
//  EggTimerViewModel.kt
   //TODO Step 1.15 call cancel notification
    val notificationManager =
       ContextCompat.getSystemService(
            app,
            NotificationManager::class.java
        ) as NotificationManager
    notificationManager.cancelNotifications()       
  1. 앱을 실행하고 타이머를 시작합니다.
  2. 알림이 표시되면 타이머를 다시 시작하고 앱이 상태 표시줄에서 이전 알림을 자동으로 삭제하는 방법을 관찰합니다.

알림 프레임워크는 개발자가 맞춤 작업을 설정하고 필요에 따라 알림의 스타일을 지정할 수 있도록 다양한 맞춤설정 옵션을 제공합니다. 이 작업을 통해 에그 타이머 알림을 맞춤설정하는 방법을 알아봅니다.

1단계: 알림 스타일 지정

필요에 맞게 알림 스타일을 지정하면 알림 내용이 눈에 잘 띄고 애플리케이션의 확장 프로그램처럼 보입니다. 알림 프레임워크에는 도움이 되는 몇 가지 스타일이 내장되어 있으며 언제든지 직접 만들 수 있습니다.

NotificationCompat에서는 다음 항목에 내장된 스타일을 제공합니다.

  • BigTextStyle: 펼친 경우 이메일 콘텐츠를 표시하는 등 큰 텍스트 블록을 표시할 수 있습니다.
  • BigPictureStyle: 대용량 이미지 첨부파일이 포함된 큰 형식의 알림을 표시합니다.
  • 대화 스타일 텍스트 콘텐츠를 표시하는 InboxStyle
  • MediaStyle: 미디어 재생 컨트롤을 표시합니다.
  • MessagingStyle: 인원수에 관계없이 여러 메시지가 포함된 큰 형식의 알림을 표시합니다.

다른 스타일에 관한 자세한 내용은 확장 가능한 알림 만들기 문서를 참고하세요. 이 단계에서는 NotificationCompat.BigPictureStyle를 사용하여 확장 시 큰 계란 이미지를 표시하는 확장형 알림을 만듭니다.

  1. NotificationUtils.kt를 열고 sendNotification() 함수를 찾습니다.
  2. 먼저 BitmapFactory를 사용하여 resources에서 이미지를 로드합니다.
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
  1. BigPictureStyle를 만들고 이미지를 설정합니다.
  2. 알림이 펼쳐질 때 큰 아이콘이 사라지도록 bigLargeIcon()null로 설정합니다.
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
val bigPicStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(eggImage)
        .bigLargeIcon(null)
  1. setStyle()로 스타일을 bigPicStyle로 설정합니다.
  2. setLargeIcon()를 사용하여 큰 아이콘을 eggImage로 설정하면 알림이 접힐 때 이미지가 더 작은 아이콘으로 표시됩니다.
// NotificationUtils.kt
// TODO: Step 2.1 add style to builder
.setStyle(bigPicStyle)
.setLargeIcon(eggImage)
  1. 앱을 실행하고 타이머를 설정합니다. 알림이 처음 표시될 때 알림 창의 접힌 상태입니다. 알림을 펼치면 확장된 알림 영역에 큰 이미지가 표시됩니다.

2단계: 알림 작업

알림 작업은 알림에 추가할 수 있는 또 다른 맞춤설정입니다. 사용자가 현재 알림을 클릭하면 앱으로 알림이 리디렉션됩니다. 이 기본 알림 작업 외에도 알림에서 앱 관련 작업을 완료하는 작업 버튼을 추가할 수 있습니다.

알림에서는 최대 3개의 작업 버튼을 제공할 수 있으며, 이를 통해 사용자가 알림을 일시 중지하거나 문자 메시지에 답장하는 등 빠르게 응답할 수 있습니다. 이러한 작업 버튼은 사용자가 알림을 탭할 때 수행되는 작업을 복제해서는 안 됩니다.

작업 버튼을 추가하려면 빌더의 addAction() 함수에 PendingIntent을 전달합니다. 이는 활동을 실행하는 대신 setContentIntent()를 호출하여 알림의 기본 탭 작업을 설정하는 것과 비슷합니다. 즉, 백그라운드에서 작업을 실행하는 BroadcastReceiver를 시작하는 등 다양한 작업을 할 수 있습니다. 그러면 작업이 이미 열려 있는 앱이 중단되지 않습니다.

이 Codelab에서는 이미 SnoozeReceiver이라는 BoadcastReceiver를 받았습니다. SnoozeReceiver를 사용하여 알림 작업에서 사용자가 클릭하게 됩니다. 다음 단계에서는 사용자가 일시중지 작업 버튼을 클릭하면 60초 동안 에그 타이머 알림을 일시중지하는 코드를 추가합니다. 일시중지 작업이 클릭되면 SnoozeReceiver는 인텐트를 수신하고 60초 후에 새 알림을 보내기 위한 새 알람을 만듭니다.

  1. SnoozeReceiver.kt를 엽니다. 이 클래스는 이전에 사용한 AlarmReceiver와 유사합니다. 다음 단계에서는 SnoozeReceiveronReceive() 함수를 트리거하는 코드를 추가합니다. 간단히 말해 SnoozeReceiver의 코드는 1분 후에 새 알림을 보낼 새 알람을 만듭니다. onReceive 함수 하단으로 아래로 스크롤하여 시스템에서 notificationManager의 인스턴스를 가져온 후 cancelAll을 호출합니다.
// SnoozeReceiver.kt
        val notificationManager = ContextCompat.getSystemService(
            context,
            NotificationManager::class.java
        ) as NotificationManager
        notificationManager.cancelAll()
  1. SnoozeReceiver를 사용하려면 NotificationUtils.kt를 엽니다.
  2. sendNotification() 함수의 스타일 바로 뒤에 SnoozeReceiver의 새 Intent snoozeIntent를 만듭니다.
  3. 다음 단계에서 매개변수를 예상하는 PendingIntentgetBroadcast() 메서드를 호출하여 대기 중인 인텐트를 만듭니다. 이 PendingIntent는 사용자가 스누즈 버튼을 탭하면 60초 후에 새 알림을 게시하기 위한 새 알람을 설정하는 데 사용됩니다.
  4. 첫 번째 매개변수는 PendingIntent가 활동을 시작해야 하는 애플리케이션 컨텍스트입니다.
  5. 두 번째 매개변수는 요청 코드입니다. 이 대기 중인 인텐트의 요청 코드입니다. 대기 중인 인텐트를 업데이트하거나 취소해야 하는 경우 이 코드를 사용하여 대기 중인 인텐트에 액세스해야 합니다.
  6. 다음으로, 실행할 활동의 인텐트인 snoozeIntent 객체를 추가합니다.
  7. 마지막으로 인텐트가 한 번만 사용되므로 #FLAG_ONE_SHOT의 플래그 값을 추가합니다. 첫 번째 탭 후에는 빠른 작업과 알림이 사라지므로 인텐트를 한 번만 사용할 수 있습니다.
// NotificationUtils.kt

// TODO: Step 2.2 add snooze action
val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java)
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(
    applicationContext, 
    REQUEST_CODE, 
    snoozeIntent, 
    FLAGS
)
  1. 그런 다음 notificationBuilder에서 addAction() 함수를 호출하세요. 이 함수에는 작업을 설명하는 아이콘과 텍스트가 필요합니다. snoozeIntent도 추가해야 합니다. 이 인텐트는 작업을 클릭할 때 올바른 boadcastReceiver를 트리거하는 데 사용됩니다.
// NotificationUtils.kt
// TODO: Step 2.3 add snooze action
.addAction(
    R.drawable.egg_icon, 
    applicationContext.getString(R.string.snooze),
    snoozePendingIntent
)
  1. 에그 타이머 앱을 실행하여 일시중지 기능을 테스트합니다.
  2. 타이머를 실행하고 앱을 백그라운드 상태로 전환합니다. 타이머가 실행되면 알림을 펼치면 알림에서 1분 동안 에그 타이머를 일시중지하는 일시중지 작업 버튼이 표시됩니다.

3단계: 알림 중요도

중요도는 알림이 사용자의 시각적 및 청각 활동을 방해하는 수준을 결정합니다. 중요도가 높은 알림은 사용자를 방해합니다.

NotificationChannel 생성자에서 중요도 수준을 지정해야 합니다. 원래 달걀 타이머 앱의 중요도를 낮게 설정했습니다. IMPORTANCE_NONE(0)부터 IMPORTANCE_HIGH(4)까지의 중요도 수준 중 하나를 사용하면 됩니다. 채널에 할당하는 중요도 수준은 채널에 게시하는 모든 알림 메시지에 적용됩니다.

채널 중요도 수준

사용자에게 표시되는 중요도 수준

중요도 (Android 8.0 이상)

우선순위 (Android 7.1 이하)

알림음이 울리며 헤드업 알림 (화면 상단에 팝업)으로 표시됩니다.

IMPORTANCE_HIGH

PRIORITY_HIGH / PRIORITY_MAX

알림음이 울립니다.

IMPORTANCE_DEFAULT

PRIORITY_DEFAULT

음소거

IMPORTANCE_LOW

PRIORITY_LOW

알림음이 없고 상태 표시줄에 표시되지 않습니다.

IMPORTANCE_MIN

PRIORITY_MIN

적절한 우선순위 수준을 선택하는 방법에 관한 자세한 내용은 알림 디자인 가이드의 '우선순위 수준'을 참조하세요. 앱에서 알림의 중요도 수준을 선택할 때는 신중해야 합니다. 사용자의 시간과 주의를 고려하여 채널 중요도를 선택해야 합니다. 중요하지 않은 알림이 긴급으로 위장되면 불필요한 알람이 울리고 주의를 산만하게 할 수 있습니다. 사용자는 알림의 중요도 수준을 전적으로 제어할 수 있으므로, 방해되는 알림을 만들 경우 알림 채널을 완전히 사용 중지할 수 있습니다.

1.6단계에서 알림을 처음 만들었을 때, 알림에 대한 사용자의 방해를 받지 않도록 설계된 달걀 타이머는 우선순위가 낮은 알림을 보내도록 설정되어 있었습니다. 하지만 달걀이 과부하되기 전에 사용자의 관심을 끄는 것이 좋습니다. 알림의 중요도 수준을 변경하려면 먼저 채널 설정을 사용하세요. 채널 중요도는 채널에 게시되는 모든 알림의 중단 수준에 영향을 미치며 NotificationChannel 생성자에서 지정되어야 합니다.

  1. 앱 알림 채널의 중요도 수준을 변경하려면 EggTimerFragment.kt을(를) 열고 createChannel()(으)로 이동하세요. 중요도 수준을 IMPORTANCE_LOW에서 IMPORTANCE_HIGH로 변경합니다.
// EggTimerFragment.kt
    val notificationChannel = NotificationChannel(
        channelId,
        channelName,
        // TODO: Step 2.4 change importance
        NotificationManager.IMPORTANCE_HIGH
    )

Android 7.1 (API 수준 25) 이하를 실행하는 기기를 지원하려면 NotificationCompat 클래스의 우선순위 상수를 사용하여 각 알림에 setPriority()를 호출해야 합니다.

  1. NotificationUtils.kt를 열고 알림 빌더 객체에 다음을 추가합니다.
// NotificationUtils.kt
   .addAction(
       R.drawable.common_google_signin_btn_icon_dark,
       applicationContext.getString(R.string.snooze),
       snoozePendingIntent
    )
   // TODO: Step 2.5 set priority
    .setPriority(NotificationCompat.PRIORITY_HIGH)
  1. 앱을 실행하기 전에 기기 또는 에뮬레이터에서 앱 아이콘을 길게 클릭한 다음, 제거를 선택하여 이전 채널 설정을 삭제하세요. 앱을 제거하지 않으면 채널 우선순위 설정이 변경되지 않으며 알림이 게시될 때 동작이 변경되지 않습니다.
  2. 이제 앱을 다시 실행하고 타이머를 시작합니다. 이번에는 알림이 전송되면 앱이 포그라운드에서 실행되거나 백그라운드에서 실행 중인지와 관계없이 팝업이 화면 상단에 표시됩니다.

4단계: 알림 배지

알림 배지는 앱에 활성 알림이 있을 때 연결된 앱의 런처 아이콘에 표시되는 작은 점입니다. 사용자가 앱 아이콘을 길게 눌러 알림을 표시할 수 있습니다.

배지라고 하는 이 점은 기본적으로 표시되는데 앱에서 취해야 할 조치는 없습니다. 그러나 배지에 대한 이해가 잘 되지 않는 상황이 있을 수 있으므로 NotificationChannel 객체에서 setShowBadge(false)를 호출하여 채널별로 사용 중지할 수 있습니다. 에그 타이머는 특정 시간에 하나의 알림만 있기 때문에 앱 아이콘의 배지는 사용자에게 많은 혜택을 제공하지 않습니다. 다음 단계에서는 배지를 사용 중지하고 달걀 타이머에 대한 알림만 표시합니다.

  1. 달걀 타이머의 채널 생성 코드에 setShowBadge(false)를 추가하여 배지를 사용 중지합니다.
// EggTimerFragment.kt

    ).apply {
        // TODO: Step 2.6 disable badges for this channel
        setShowBadge(false)
    }
  1. 앱을 다시 실행하고 타이머를 시작한 후 앱 아이콘을 확인합니다. 앱 아이콘에 배지가 표시되지 않습니다.

솔루션 코드는 다운로드된 코드의 마스터 분기에 있습니다.

Udacity 과정:

Android 개발자 문서:

이 과정의 다른 Codelab에 관한 링크는 Kotlin Codelab의 고급 Android 방문 페이지를 참고하세요.