Notification in Android ตอนที่ 5 — Notification Channel

ในบทความนี้เจ้าของบล็อกจะมาพูดรายละเอียดและการทำงานของ Notification Channel ที่เป็น 1 ในสิ่งที่นักพัฒนาต้องใส่ทุกครั้งเพื่อให้ Notification ทำงานได้บนแอนดรอยด์เวอร์ชันใหม่ๆกัน

บทความในซีรีย์เดียวกัน

Notification Channel สิ่งที่ถูกเพิ่มเข้ามาใน Android 8.0 Oreo (API 26)

เพื่อช่วยให้นักพัฒนาจัดกลุ่มของ Notification ให้เหมาะสมกับแอป เพราะว่าแอปที่ใช้งานจริงในปัจจุบันนั้นมักจะมี Notification เยอะแยะมากมายเต็มไปหมด ซึ่งแต่ละอันก็มีจุดประสงค์ในการแจ้งเตือนที่ต่างกัน แต่การแสดง Notification ที่เยอะมากเกินไปก็อาจจะถูกผู้ใช้ปิด Notification ในแอปไปเลยก็ได้

ซึ่งแอนดรอยด์ในเวอร์ชันเก่าๆนั้นสิ่งที่เกี่ยวกับ Notification ที่ผู้ใช้สามารถกำหนดใน App Info ได้จะมีแต่การตั้งค่าที่ส่งผลกับ Notification ทั้งหมดในแอปนั้นๆ

เอาเข้าจริงก็ไม่ค่อยสะดวกกับผู้ใช้และนักพัฒนาซักเท่าไร จึงทำให้แอปอย่าง Facebook ในช่วงเวลานั้นถึงกับต้องทำหน้าต่างตั้งค่าการแสดง Notification แยกตามประเภทไว้ในแอปให้เลย

แต่คงไม่สนุกซักเท่าไรถ้าผู้ที่หลงเข้ามาอ่านต้องทำแบบนี้กับแอปของตัวเองบ้าง

จึงทำให้ Android 8.0 Oreo (API 26) มีการเพิ่ม Notification Channel เข้ามาเพื่ออำนวยความสะดวกให้กับผู้ใช้

โดยผู้ใช้สามารถตั้งค่าในการแสดงผลของ Notification ในแต่ละ Channel แยกกันได้เลย เช่น เจ้าของบล็อกไม่ต้องการให้แอปแสดงแค่ Notification บางประเภทเท่านั้น ก็สามารถปิดเฉพาะ Channel นั้นทิ้งได้เลย ส่วน Channel อื่นก็ยังแสดงได้ปกติ

แล้วนักพัฒนาจะจัดกลุ่มให้กับ Notification ยังไงได้บ้าง

Notification Channel ไม่ได้จัดกลุ่มไว้ให้นักพัฒนาตั้งแต่แรก สิ่งที่นักพัฒนาต้องทำคือสร้าง Notification Channel ขึ้นมาเอง แล้วแบ่งกลุ่มตามใจชอบได้เลย

โดย Notification Channel จะมีแยกระหว่าง Channel Group กับ Channel ให้ด้วย ในกรณีที่แอปมีความหลากหลายของ Notification สูงมาก เลยต้องมีการแบ่งเป็นกลุ่มย่อยอีกที

และถ้า Channel ทั้งหมดไม่ได้อยู่ใน Channel Group ก็จะไม่แสดง Channel Group (เวอร์ชันใหม่ๆ) หรือแสดงเป็น Default Channel Group (เวอร์ชันเก่าๆ)

ในกรณีที่แอปมี Channel Group แต่ว่ามีบาง Channel ที่ไม่ได้อยู่ใน Channel Group ใดๆ ระบบก็จะแสดงให้อยู่ในกลุ่มของ Other แทน

ซึ่งนักพัฒนาจะต้องเตรียม Notification Channel ไว้ให้เรียบร้อยก่อนที่จะใช้คำสั่งแสดง Notification ในแอป เพราะบน Android 8.0 Oreo (API 26) ขึ้นไปจะไม่แสดง Notification ถ้ายังไม่ได้สร้าง Notification Channel ของ Notification นั้นก่อน

สิ่งที่นักพัฒนาต้องทำเพื่อรองรับ Notification Channel

จัดกลุ่มของ Notification ที่มีในแอปทั้งหมด

ก่อนที่จะเริ่มเขียนโค้ดสำหรับ Notification Channel ให้ตรวจสอบก่อนว่า Notification ทั้งหมดที่มีอยู่ในแอปมีแบบไหนบ้าง สามารถจุดกลุ่มแบบไหนได้บ้าง โดยจะแยกตาม Business ของแอป หรือว่าแยกตามหน้าที่ของ Notification แต่ละตัวก็ได้ ขึ้นอยู่กับความเหมาะสม

สร้าง Channel และ Channel Group เพื่อแบ่งกลุ่ม Notification ตามที่กำหนดไว้

เป็นขั้นตอนที่นักพัฒนาจะต้องเพิ่มคำสั่งเข้าไปในแอปนั่นเอง โดยคำสั่งสำหรับสร้าง Notification Channel จะมีลักษณะแบบนี้

val context: Context = /* ... */
val channelId = "new_promotion"
val channelName = "New Promotion"
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)

val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)

โดยสิ่งที่นักพัฒนาจะต้องกำหนดให้กับ Channel จะมี 3 อย่างด้วยกันคือ

  • ID ของ Channel
  • ชื่อของ Channel เพื่อใช้แสดงให้ผู้ใช้เห็นเมนู Notification ของ App Info
  • ระดับความสำคัญ (Importance) ของ Channel
สามารถกำหนด Channel ID และ Channel Name เหมือนกันเลยก็ได้

และถ้าต้องการสร้าง Channel Group ด้วย ก็จะต้องเพิ่มคำสั่งของ Channel Group เพิ่มเข้ามาด้วย

val context: Context = /* ... */

val groupId = "announcement"
val groupName = "Announcement"
val channelGroup = NotificationChannelGroup(groupId, groupName)

val channelId = "new_promotion"
val channelName = "New Promotion"
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
channel.group = channelGroup

val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannelGroup(channelGroup)
manager.createNotificationChannel(channel)

โดย Channel Group ก็จะต้องกำหนด ID และชื่อของ Group ด้วยเช่นกัน

และที่สำคัญสำหรับการสร้าง Channel และ Channel Group ก็คือจะต้องสร้าง Channel Group ก่อน Channel เสมอ

java.lang.IllegalArgumentException: NotificationChannelGroup doesn't exist

เพราะถ้านักพัฒนาสร้าง Channel ก่อน แต่ยังไม่มี Channel Group จะทำให้เกิด Exception แบบข้อความข้างบนนี้ได้

แล้วต้องใส่คำสั่งสร้าง Channel และ Channel Group ไว้ที่ไหน?

เจ้าของบล็อกแนะนำให้ใส่ไว้ในคลาส Application เพื่อความมั่นใจว่าจะถูกสร้างทันทีที่แอปเริ่มทำงาน ซึ่ง Notification Channel สามารถสร้างทับของเก่าได้ ไม่ส่งผลกระทบอะไรต่อการทำงานของ Notification

class AwesomeApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        initNotificationChannel(applicationContext)
    }

    private fun initNotificationChannel(context: Context) {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelId = "new_promotion"
            val channelName = "New Promotion"
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)

            val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }
    }
}

และควรเช็คเวอร์ชันของอุปกรณ์แอนดรอยด์ทุกครั้งก่อนเรียกใช้คำสั่งของ Notification Channel เพื่อให้มั่นใจว่าคำสั่งจะทำงานก็ต่อเมื่อเป็น Android 8.0 Oreo (API 26) ขึ้นไปเท่านั้น แต่ถ้าแอปรองรับแต่เวอร์ชันที่สูงกว่านั้นก็ไม่จำเป็นต้องใส่ก็ได้

กำหนด Channel ให้กับ Notification ทุกครั้งเมื่อเรียกใช้งาน

คำสั่งที่ใช้ในการสร้าง Notification จะยังคงเหมือนเดิม เปลี่ยนแค่ตรง Constructor เวลาสร้าง Notification ที่จะต้องกำหนด Channel ID เพิ่มเข้าไปด้วย

val context: Context = /* ... */
val channelId = "new_promotion"
val notification: Notification = NotificationCompat.Builder(context, channelId).apply { 
    /* ... */
}.build()

ในกรณีที่กำหนด Channel ID ไม่ถูกต้อง หรือ Channel นั้นยังไม่ได้ถูกสร้าง ก็จะทำให้ Notification ไม่แสดงนั่นเอง

Notification Channel สามารถการกำหนดค่าอะไรได้บ้าง

นอกจาก Channel ID, ชื่อของ Channel และ Importance แล้ว ก็ยังค่าอีกหลายอย่างที่นักพัฒนาสามารถกำหนดได้ ดังนี้

Allow Bubbles

กำหนดว่าจะให้ Channel นี้รองรับฟีเจอร์ Bubbles หรือไม่ สามารถดูข้อมูลเพิ่มเติมของฟีเจอร์ได้ที่ User Interfaces — Bubbles [Android Developers]

Bubbles | Android Developers

โดยคำสั่งนี้จะใช้ได้ใน Android 10 (API 29) ขึ้นไปเท่านั้น

val channel: NotificationChannel = /* ... */
channel.setAllowBubbles(true)
/* ... */

Bypass Do Not Disturb

กำหนดว่าจะให้ Notification สามารถแสดงได้อยู่ถึงแม้ว่าจะอยู่ในโหมด Do Not Disturb หรือไม่

val channel: NotificationChannel = /* ... */
channel.setAllowBubbles(true)
/* ... */

Conversation ID

ในกรณีที่เป็น Channel สำหรับ Notification ประเภทบทสนทนาจะสามารถกำหนด Conversation ID ได้ สามารถดูข้อมูลเพิ่มเติมของฟีเจอร์นี้ได้ที่ Android 11 — Conversations [Android Developers]

People and conversations | Android Developers

โดยคำสั่งนี้จะใช้ได้ใน Android 11 (API 30) ขึ้นไปเท่านั้น

val channel: NotificationChannel = /* ... */
channel.setConversationId("parent_channel_id", "conversation_id")
/* ... */

Description

กำหนดคำอธิบายสำหรับ Channel

val channel: NotificationChannel = /* ... */
channel.description = "Notify when the app has new update with the new major features"
/* ... */

โดยจะแสดงให้ผู้ใช้เห็นที่บรรทัดสุดท้ายในหน้าตั้งค่า Notification ของ Channel นั้นๆ

Lock Screen Visibility

กำหนดว่าจะให้แสดงข้อมูลของ Notification ของ Channel นั้นๆหรือไม่ ในระหว่างเครื่องยังไม่ได้ปลดล็อคหน้าจอ ซึ่งกำหนดได้ทั้งหมด 3 แบบด้วยกัน

  • Public : แสดงข้อมูลทั้งหมดตามปกติ
  • Private : แสดงให้เห็นว่ามี Notification แต่ไม่สามารถเปิดดูได้จนกว่าจะปลดล็อคหน้าจอ
  • Secret : ไม่แสดง Notification จนกว่าจะปลดล็อคหน้าจอ
val channel: NotificationChannel = /* ... */
channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
/* ... */

สำหรับการแสดงผลแบบ Private จะมีผลก็ต่อเมื่อผู้ใช้ตั้งค่าใน Settings ให้ Privacy ของ Lock Screen เป็นแบบ Show sensitive content only when unlocked เท่านั้น

ซึ่งจะทำให้ Notification ถูกย่อและไม่สามารถอ่านเนื้อหาได้จนกว่าจะปลดล็อคหน้าจอ

นอกจากนี้การกำหนดค่าเป็น Private นักพัฒนายังสามารถสร้าง Notification อีกตัวที่เรียกว่า Public Notification เพื่อใช้แสดงในกรณียังไม่ได้ปลดล็อคหน้าจอได้อีกด้วย

Show Badge

กำหนดว่าจะให้แสดง Notification Badge บน Home Screen หรือไม่

คำสั่งนี้มีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val channel: NotificationChannel = /* ... */
channel.setShowBadge(true)
/* ... */

Notification Badge ถูกเพิ่มเข้ามาใน Android 8.0 Oreo (API 26) ขึ้นไปเท่านั้น คำสั่งนี้จึงไม่ส่งผลกับอุปกรณ์แอนดรอยด์ยี่ห้อต่างๆที่มี Notification Badge ทั้งแต่แรกอยู่แล้ว

Sound

กำหนดไฟล์เสียงที่จะใช้เล่นเวลาที่แสดง Notification ที่อยู่ใน Channel นั้นๆ แทนเสียง Default ของเครื่อง

คำสั่งนี้จะมีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val context: Context = /* ... */
val uri = Uri.parse("android.resource://${context.packageName}/${R.raw.sound_notification}")
val attributes = AudioAttributes.Builder().apply {
    setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
}.build()
val channel: NotificationChannel = /* ... */
channel.setSound(uri, attributes)
/* ... */

จากเดิมนักพัฒนาสามารถกำหนดใน Notification ได้โดยตรง แต่สำหรับ Android 8.0 Oreo (API 26) ขึ้นไปจะใช้ค่าที่กำหนดไว้ใน Notification Channel แทน

Enable Vibration

เปิด/ปิดการสั่นเวลาแสดง Notification ของ Channel นั้นๆ

คำสั่งนี้จะมีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val channel: NotificationChannel = /* ... */
channel.enableVibration(true)
/* ... */

Vibration Pattern

กำหนดรูปแบบในการสั่นเมื่อ Notification ของ Channel ได้เปิดการสั่นไว้ แทนการสั่นด้วยรูปแบบ Default ของเครื่อง

คำสั่งนี้จะมีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val channel: NotificationChannel = /* ... */
channel.vibrationPattern = longArrayOf(50, 1000, 50, 1000)
/* ... */

จากเดิมนักพัฒนาสามารถกำหนดใน Notification ได้โดยตรง แต่สำหรับ Android 8.0 Oreo (API 26) ขึ้นไปจะใช้ค่าที่กำหนดไว้ใน Notification Channel แทน

Enable Lights

เปิด/ปิดการแสดงไฟแจ้งเตือนในเวลาแสดง Notification ของ Channel นั้นๆ ซึ่งจะรองรับเฉพาะเครื่องที่มีไฟ LED สำหรับแจ้งเตือนเท่านั้น

คำสั่งนี้จะมีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val channel: NotificationChannel = /* ... */
channel.enableLights(true)
/* ... */

จากเดิมนักพัฒนาสามารถกำหนดใน Notification ได้โดยตรง แต่สำหรับ Android 8.0 Oreo (API 26) ขึ้นไปจะใช้ค่าที่กำหนดไว้ใน Notification Channel แทน

Light Color

กำหนดสีด้วยค่าแบบ ARGB ของไฟ LED สำหรับเครื่องที่มีไฟ LED สำหรับแจ้งเตือน โดยจะต้องเปิดการแสดงไฟแจ้งเตือนด้วย ซึ่งจะรองรับเฉพาะเครื่องที่สามารถเปลี่ยนสีของไฟ LED ได้เท่านั้น

คำสั่งนี้จะมีผลเฉพาะตอนที่ Channel ถูกสร้างขึ้นมาครั้งแรกเท่านั้น ไม่สามารถใช้คำสั่งนี้ซ้ำเพื่อแก้ไขค่าในภายหลังได้

val channel: NotificationChannel = /* ... */
channel.lightColor = Color.parse("#FF0000")
/* ... */

จากเดิมนักพัฒนาสามารถกำหนดใน Notification ได้โดยตรง แต่สำหรับ Android 8.0 Oreo (API 26) ขึ้นไปจะใช้ค่าที่กำหนดไว้ใน Notification Channel แทน

ผู้ใช้สามารถแก้ไขการตั้งค่าของ Notification Channel เองได้

ซึ่งการกำหนดค่าตอนสร้าง Channel จะเป็นการกำหนดในตอนเริ่มต้นเท่านั้น เพราะผู้ใช้ก็สามารถเข้าไปแก้ไขการตั้งค่าดังกล่าวในเมนู Notification ของ App Info ได้ตามใจชอบ

Importance ใน Notification Channel

ในเวอร์ชันก่อน Android 8.0 Oreo (API 26) จะมีการกำหนดความสำคัญของ Notification แต่ละตัวด้วย Priority แต่พอมี Notification Channel เพิ่มเข้ามา ก็ได้ยกเลิก Priority และเปลี่ยนมาใช้ Importance ของแต่ละ Notification Channel แทน

ซึ่ง Importance เป็นการกำหนดความสำคัญของ Notification ที่อยู่ในแต่ละ Channel ซึ่งจะส่งผลต่อรูปแบบในการแสดงผลของ Notification Drawer และการแจ้งเตือนให้ผู้ใช้เห็นด้วย ซึ่ง Channel ไหนมีความสำคัญสูงก็จะแสดงผลเด่นขึ้น ผู้ใช้สามารถเห็นได้ง่ายขึ้น ในขณะที่ Channel ที่มีความสำคัญต่ำก็จะถูกแสดงให้สะดุดตาน้อยกว่า รบกวนสายตาผู้ใช้น้อยลง

โดยนักพัฒนาสามารถกำหนด Importance ได้ทั้งหมดดังนี้

NotificationManager.IMPORTANCE_MAX
NotificationManager.IMPORTANCE_HIGH
NotificationManager.IMPORTANCE_DEFAULT
NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_MIN
NotificationManager.IMPORTANCE_NONE
NotificationManager.IMPORTANCE_UNSPECIFIED

แต่ในการใช้งานจริงจะนิยมกำหนดอยู่แค่ 3 แบบเท่านั้น คือ Max, Default และ Low

val importance = NotificationManager.IMPORTANCE_HIGH
val id = "new_promotion"
val name = "New Promotion"
val channel: NotificationChannel = NotificationChannel(id, name, importance)

เมื่อนักพัฒนาสร้าง Notification เพื่อแสดงให้ผู้ใช้ ก็จะแสดงในรูปแบบต่างๆตามปัจจัยต่างๆของเครื่องและตามที่กำหนด Importance ใน Channel นั้นๆ

การแก้ไข/ลบ Notification Channel

เมื่อต้องการเปลี่ยนแปลง Channe และ Channel Group สามารถใช้คำสั่งเพื่อเรียก Channel ต่างๆที่มีอยู่ในแอปได้เลย โดยระบุ Channel ที่ต้องการแก้ไขด้วย Channe ID

val manager: NotificationManager = /* ... */
val channelId = "new_promotion"
manager.getNotificationChannel(channelId)?.let { channel ->
    // Update new channel name
    channel.name = "Awesome Promotion"
    manager.createNotificationChannel(channel)
}

ถ้าต้องการลบ Channel ที่ไม่ต้องการแล้ว สามารถใช้คำสั่งแบบนี้ได้เลย

val manager: NotificationManager = /* ... */
val channelId = "new_promotion"
manager.deleteNotificationChannel(channelId)

ในการลบ Channel ก็จะต้องระบุด้วย Channel ID เช่นกัน

ของแถม — คำสั่งเปิดหน้าตั้งค่า Notification และ Notification Channel

ในกรณีที่ต้องการให้ผู้ใช้เข้าไปที่หน้าตั้งค่า Notification หรือ Notification Channel แต่อยากอำนวยความสะดวกให้สามารถกดจากแอปได้เลย เพราะถ้าให้ผู้ใช้กดเข้าไปเองอาจจะไม่ใช่เรื่องง่ายสำหรับผู้ใช้ทุกคน

คำสั่งสำหรับเข้าหน้าตั้งค่า Notification ของแอป

val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
startActivity(intent)

คำสั่งสำหรับเข้าหน้าตั้งค่า Notification Channel ที่ต้องการ

val context: Context = /* ... */
val channelId = "new_promotion"
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
startActivity(intent)

สรุป

ถึงแม้ว่า Notification Channel อาจจะไม่ใช่ฟีเจอร์ที่สำคัญอะไรมากนักสำหรับนักพัฒนาส่วนใหญ่ แต่เมื่อลองมองดูแอปส่วนใหญ่ในตลาด ก็ต้องบอกเลยว่า Notification ถือว่าเป็นสิ่งที่สำคัญมากในการทำให้เกิด User Retention

สำหรับแอปที่มี Notification จำนวนเยอะมากจะไม่สามารถเลี่ยงกรณีที่ผู้ใช้รำคาญได้อยู่แล้ว จึงควรแยกกลุ่มเพื่อให้ผู้ใช้สามารตั้งค่าสำหรับแต่ละประเภทได้ ซึ่งจะช่วยลดโอกาสที่จะโดนผู้ใช้สั่งปิด Notification ทั้งหมดของแอปได้

อย่างน้อยการโดนปิด Notification บางส่วนก็ยังดีกว่าโดนปิดทั้งหมดแหละนะ

แหล่งข้อมูลอ้างอิง