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

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

การอัปเดตข้อมูลใน Notification ใหม่

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

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

ดังนั้นนักพัฒนาจึงมีวิธีในการจัดการกับ Notification ID อยู่ 2 วิธีด้วยกัน คือ

กำหนดเป็น Constant ไว้ในคลาสนั้นๆ

เหมาะกับ Notification ที่ทำงานแค่ตัวเดียว ไม่มีตัวอื่นมาทำงานทับซ้อน

class MainActivity : AppCompatActivity() {
    companion object {
        private const val DEFAULT_NOTIFICATION_ID = 3279
    }
    ...
}

สร้างแล้วเก็บไว้ในตัวแปร

เหมาะกับ Notification ที่ทำงานได้มากกว่า 1 ตัว ในช่วงเวลาเดียวกัน

val ongoingNotificationIds = arrayListOf<Int>()

fun showUpdateableNotification(context: Context) {
    val notification: Notification = ...
    val manager: NotificationManager = ...
    val id = Random.nextInt()
    ongoingNotificationIds.add(id)
    manager?.notify(id, notification)
}

คำสั่งอื่นๆของ Notification ที่สำคัญต่อการอัปเดตข้อมูลในภายหลัง

นอกจาก Notification ID แล้ว ยังมีคำสั่งอีก 2 ตัว นั่นก็คือ

Only Alert Once

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

val notification = NotificationCompat.Builder(...).apply {
    ...
    setOnlyAlertOnce(true)
}.build()

โดย True คือให้แจ้งเตือนแค่ครั้งแรกครั้งเดียว และถ้ากำหนดเป็น False จะเป็นการแจ้งเตือนทุกครั้ง

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

แต่ถ้าอัปเดตไม่ค่อยบ่อยมาก และการอัปเดตข้อมูลในแต่ละครั้งจำเป็นต้องให้ผู้ใช้รับรู้ด้วย ก็ไม่ควรใช้ Only Alert At Once

On Going

เป็นการกำหนดให้ Notification อยู่ในสถานะ On Goging หรือก็คือกำลังทำงานบางอย่างอยู่ โดยผู้ใช้และระบบจะไม่สามารถปิด Notification ทิ้งได้จนกว่าจะสั่งผ่านโค้ด หรือผู้ใช้สั่ง Force Close หรือลบแอปทิ้ง

val notification = NotificationCompat.Builder(...).apply {
    ...
    setOngoing(true)
}.build()

ในการเปิดใช้งาน On Going ห้ามลืมสั่งปิด Notification เมื่อทำงานเสร็จทุกครั้ง ไม่เช่นนั้นจะเจอปัญหา Notification แสดงค้างอยู่บน Notification Drawer โดยที่ผู้ใช้ไม่สามารถปิดได้โดยตรง ต้องสั่ง Force Close หรือลบแอปทิ้ง ซึ่งไม่ได้เรื่องที่ดีซักเท่าไร

มาดูตัวอย่างกันดีกว่า

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

สมมติว่าคำสั่งในการดาวน์โหลดไฟล์มีหน้าตาเป็นแบบนี้

getAwesomeFile(object : DownloadListener {
    override fun onStarted() {
    }

    override fun onProgressUpdated(progress: Int, total: Int) {
    }

    override fun onCompleted(isSuccess: Boolean, uri: Uri?) {
    }
})

สิ่งที่เจ้าของบล็อกต้องทำมีทั้งหมด 3 ส่วนด้วยกันคือ

  • เริ่มทำการโหลดไฟล์ — แสดง Notification แบบ Progress Style
  • อัพเดทสถานะการโหลดไฟล์ — อัปเดตข้อมูล Progress ใน Notification ตัวเดิม
  • โหลดไฟล์เสร็จแล้ว — เปลี่ยนให้ Notification แสดงแบบ Standard Style เพื่อแจ้งว่าโหลดไฟล์เสร็จแล้ว

เนื่องจากมีทั้ง Progress Style และ Standard Style เจ้าของบล็อกจึงเตรียมคำสั่งสำหรับ Notification แยกเป็น 2 คำสั่งดังนี้

คำสั่งสำหรับ Notification แบบ Progress Style

เนื่องจาก Progress Style ใช้ในการที่เริ่มดาวน์โหลดและระหว่างกำลังดาวน์โหลดอยู่ จึงเขียนออกมาเป็นคำสั่งแบบนี้

val DEFAULT_NOTIFICATION_ID = 3279

fun showDownloadProgressNotification(context: Context, progress: Int, total: Int) {
    val channelId = ...
    val notification = NotificationCompat.Builder(context, channelId).apply {
        setSmallIcon(...)
        setContentTitle("Downloading")
        setContentText("We're getting awesome data for you")
        setOngoing(true)
        setOnlyAlertOnce(true)
        setProgress(total, progress, false)
        ...
    }.build()
    val manager = NotificationManagerCompat.from(this)
    manager.notify(DEFAULT_NOTIFICATION_ID, notification)
}

โดยกำหนดให้แจ้งเตือนแค่ครั้งแรกเท่านั้น (Only Alert Once) หลังจากนั้นจะเป็นการอัปเดตค่า Progress อย่างเดียว ไม่จำเป็นต้องแจ้งเตือนซ้ำ

และกำหนดให้เป็นแบบ On Going เพื่อป้องกันผู้ใช้ปิด Notification ในระหว่างที่กำลังดาวน์โหลดไฟล์อยู่

ส่วน Progress ก็จะรับค่ามาจาก DownloadListener ว่าดาวน์โหลดไปแล้วเท่าไร จากทั้งหมดเท่าไร

คำสั่งสำหรับ Notification แบบ Standard Style

สำหรับ Standard Style จะใช้ตอนที่ดาวน์โหลดไฟล์เสร็จแล้ว จึงเขียนออกมาเป็นคำสั่งแบบนี้

val DEFAULT_NOTIFICATION_ID = 3279

fun showDownloadComplatedNotification(context: Context) {
    val channelId = ...
    val notification = NotificationCompat.Builder(context, channelId).apply {
        setSmallIcon(...)
        setContentTitle("Downloaded")
        setContentText("File was saved in your device")
        setOngoing(false)
        setOnlyAlertOnce(false)
        ...
    }.build()
    val manager = NotificationManagerCompat.from(this)
    manager.notify(DEFAULT_NOTIFICATION_ID, notification)
}

โดยปิด Only Alert Once เพื่อให้ระบบทำการแจ้งเตือนใหม่อีกครั้ง เพราะดาวน์โหลดไฟล์เสร็จแล้ว

และปิด On Going ด้วย เพื่อให้ผู้ใช้สามารถปิด Notification ทิ้งได้

จะเห็นว่า Notification ID ที่ใช้ใน Standard Style เป็นตัวเดียวกันกับตอนที่สร้าง Progress Style

รวมทั้ง 2 คำสั่งเข้าด้วยกันใน Download Listener

ได้ออกมาเป็นแบบนี้

val context: Context = this
getAwesomeFile(object : DownloadListener {
    override fun onStarted() {
        showDownloadProgressNotification(context, 0, 1)
    }
    override fun onProgressUpdated(progress: Int, total: Int) {
        showDownloadProgressNotification(context, progress, total)
    }

    override fun onCompleted(isSuccess: Boolean, uri: Uri?) {
        if(isSuccess) {
            showDownloadComplatedNotification(context)
        } else {
            // Show download failed notification
        }
    }
})

และเมื่อลองทดสอบก็จะได้ผลลัพธ์ออกมาเป็นแบบนี้

นอกจากนี้นักพัฒนายังสามารถกำหนด Content Intent แยกกันระหว่าง Notification ทั้ง 2 แบบได้อีกด้วย เช่น

fun showDownloadProgressNotification(context: Context, progress: Int, total: Int) {
    val openDownloadScreenIntent: PendingIntent = ...
    val notification = NotificationCompat.Builder(...).apply {
        ...
        setContentIntent(openDownloadScreenIntent)
        setAutoCancel(false)
    }.build()
    ...
}

fun showDownloadComplatedNotification(context: Context) {
    val openFileDirectoryIntent: PendingIntent = ...
    val notification = NotificationCompat.Builder(...).apply {
        ...
        setContentIntent(openFileDirectoryIntent)
        setAutoCancel(false)
    }.build()
    ...
}

เมื่อกดที่ Progress Style จะให้เปิดหน้าดาวน์โหลดไฟล์ในแอป (ปิด Auto Cancel ด้วยนะ) และเมื่อกดที่ Standard Style จะสั่งเปิดไฟล์ที่ดาวน์โหลดไว้ในเครื่อง (เปิด Auto Cancel ไว้ด้วย)

สรุปการอัปเดตข้อมูลให้กับ Notification ที่แสดงผลอยู่

เอาเข้าจริงหัวข้อนี้ไม่ได้มีอะไรมากมายนัก ขอแค่รู้และเข้าใจในการทำงานเพียงแค่ 3 จุดด้วยกัน

  • การอัปเดตของเดิมจะต้องกำหนด Notification ID ให้เหมือนกับของเดิม
  • ถ้าอยากจะให้แสดงแจ้งเตือนแค่ครั้งเดียว ให้กำหนด Only Alert Once เพิ่มเข้าไป
  • ถ้าไม่อยากให้ผู้ใช้ปิด Notification ในระหว่างที่ทำงานอย่าง ให้กำหนด On Going เพิ่มเข้าไป

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