จัดการกับ Token ของ Push Notification อย่างไรดี

สำหรับการพัฒนาแอปในยุคนี้เป็นเรื่องปกติที่จะต้องมีฟีเจอร์เกี่ยวกับ Push Notification ที่จะทำให้ฝั่ง Web Service สามารถส่งข้อมูล ๆ ใดให้ Client ได้ โดยที่ไม่ต้องรอให้ Client ส่ง Request เพื่อขอข้อมูลนั้น ๆ เช่น แสดงข้อความเพื่อแจ้งเตือนเพื่อบอกให้ผู้ใช้รู้ว่ามีส่วนลดภายในแอป เป็นต้น ซึ่งข้อดีของการใช้ Push Notification ก็คือผู้ใช้ไม่จำเป็นต้องเปิดแอปก็รับข้อมูลได้ (ตราบใดที่เชื่อมต่อกับอินเตอร์เน็ตอยู่)

ซึ่งการทำ Push Notification บนแอนดรอยด์ก็จะต้องพึ่งพาบริการของ Firebase Cloud Messaging อย่างเลี่ยงไม่ได้ (สำหรับอุปกรณ์แอนดรอยด์ที่ติดตั้ง Google Apps หรือ Google Play Services) และถึงแม้จะใช้บริการอย่าง OneSignal, CleverTap หรือ Blaze สุดท้ายแล้วเบื้องหลังการทำงานของบริการเหล่านี้ก็ใช้ Firebase Cloud Messaging อยู่ดี

โดยหนึ่งในขั้นตอนสำคัญของการทำ Push Notification ก็คือนักพัฒนาจะต้องรับ Token ที่ได้จาก Google Play Services ที่ติดตั้งอยู่ภายในเครื่อง แล้วส่งให้ Web Service เก็บไว้ เพื่อใช้ตอนที่ต้องการส่งข้อมูลผ่าน Push Notification ด้วย Firebase Cloud Messaging นั่นเอง

ถ้าลองอ่าน Documentation ของ Firebase Cloud Messaging ก็จะไม่ได้อธิบายขั้นตอนนี้ไปมากกว่าการบอกว่าส่ง Token ใหม่ให้ Web Service

นักพัฒนาสามารถอ่าน Documentation สำหรับการจัดการกับ Token ของ Firebase Cloud Messaging ได้ที่  Best practices for FCM registration token management [Firebase]
Best practices for FCM registration token management | Firebase Cloud Messaging

โดย Token จะถูกสร้างขึ้นใหม่ได้ในหลาย ๆ เงื่อนไข เช่น Clear App Data, หรือลบแอปแล้วติดตั้งใหม่ เป็นต้น นั่นหมายความว่าแต่ละเครื่องมีโอกาสที่ Token จะเปลี่ยนแปลงได้เสมอ และสิ่งที่นักพัฒนาต้องทำก็คือส่ง Token ใหม่ให้ Web Service ในขณะที่ Web Service ก็ควรจะลบ Token เก่าทิ้งด้วยเช่นกัน

ควรเก็บ Timestamp คู่กับ Token เสมอ เพราะถ้า Token มีอายุนานเกิน 2 เดือนจะมีโอกาสสูงมากที่ Token นั้นจะใช้งานไม่ได้แล้ว

และนี่ก็คือเรื่องราวของบทความนี้ ที่จะแนะนำวิธีส่ง Token ขึ้น Web Service อย่างไรให้เหมาะสมนั่นเอง

เพราะว่าเราเขียนโค้ดสำหรับการทำงานในส่วนนี้ได้หลายแบบ

ถึงแม้ว่าการจัดการกับ Token ของ Firebase Cloud Messaging จะฟังดูเป็นเรื่องง่าย ก็แค่ส่งขึ้นไปเก็บไว้บน Web Service ก็จบแล้ว แต่ถ้าเราเขียนโค้ดในส่วนนี้ไม่ดีพอ ก็อาจจะทำให้ผู้ใช้บางคนไม่ได้รับข้อมูลผ่าน Push Notification ก็เป็นได้

ถ้าผู้ที่หลงเข้ามาอ่านไม่อยากเสียเวลาทำความเข้าใจเกี่ยวกับโค้ดในแต่ละวิธี แนะนำให้ข้ามไปดูวิธีที่ 2 หรือ 3 แล้วเลือกใช้งานได้ตามสะดวก

วิธีที่ 1 – ได้เมื่อไร ก็ส่งไปทันที (ไม่แนะนำ)

วิธีนี้จะทำการส่งข้อมูลให้ Web Service ให้ทันทีที่ได้ Token ใหม่ในขณะนั้น

// Firebase Messaging Service
override fun onNewToken(token: String) {
    sendNewTokenToWebService(token)
}

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

วิธีที่ 2 – ลองส่งดูก่อน ถ้าไม่ได้ก็เก็บไว้ในเครื่องเพื่อส่งใหม่ตอนเปิดแอป (แนะนำ)

เพื่อรับมือกับปัญหาอุปกรณ์แอนดรอยด์ส่ง Token ให้กับ Web Service ไม่ได้ด้วยเหตุผลใด ๆ ก็ตาม จึงนำ Token ที่ได้มาเก็บไว้ใน Persistent Storage อย่าง SharedPreferences ก่อน แล้วทำการส่งให้กับ Web Service

// Firebase Messaging Service
override fun onNewToken(token: String) {
    saveNewTokenToLocalStorage(token)
    val result = sendNewTokenToWebService(token)
    if (result.succeed) {
        deleteTokenInLocalStorage()
    }
}

// Application or Main Activity
if (isTokenInLocalStorageAvailable()) {
    val result = sendNewTokenToWebService(token)
    if (result.isSuccess) {
        deleteTokenInLocalStorage()
    }
}

ถ้าส่ง Token ให้กับ Web Service สำเร็จก็ให้ลบ Token ใน Persistent Storage ทิ้งไป แต่ถ้ามีปัญหาบางอย่างเกิดขึ้น ก็จะยังคงเก็บ Token นั้นไว้อยู่ และจะส่งใหม่อีกครั้งเมื่อเปิดแอปในครั้งถัดไป

วิธีนี้จะช่วยลดปัญหาการส่ง Token ไปไม่ถึง Web Service ได้ และเป็นวิธีที่นักพัฒนาส่วนใหญ่นิยมใช้กัน เพราะเป็นวิธีที่เข้าใจได้ง่าย

แต่วิธีนี้ก็จะยังมีปัญหาว่าในระหว่างที่รอส่ง Token ครั้งถัดไป จะต้องรอจนกว่าแอปจะถูกเปิดขึ้นมาใหม่อีกครั้ง นั่นหมายความว่าในระหว่างนั้น Web Service จะส่ง Push Notification มาที่เครื่องดังกล่าวไม่ได้เช่นกัน ถึงแม้จะเป็นช่วงระยะไม่นานก็ตาม

วิธีที่ 3 – เหมือนกับวิธีที่ 2 แต่ใช้ Work Manager เข้ามาช่วย (แนะนำ)

เพื่อลดช่องว่างที่อาจจะเกิดขึ้นในวิธีที่ 2 จึงใช้ประโยชน์จาก Work Manager เพื่อคอยจัดการเรื่อง Retry ให้โดยอัตโนมัติ

class UploadTokenWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
    override fun doWork(): Result {
        val result = sendNewTokenToWebService(token)
        return if (result.succeed) {
            deleteTokenInLocalStorage()
            Result.success()
        } else {
            Result.retry()
        }
    }
}

อีกทั้งยังสามารถกำหนด Constraint เพื่อให้ทำงานในจังหวะที่เหมาะสมได้อีกด้วย

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

val workRequest = OneTimeWorkRequestBuilder<UploadTokenWorker>()
    .setConstraints(constraints)
    .build()

แล้วสั่งให้ Worker ทำงานในตอนที่ได้ Token ใหม่หรือตอนที่เปิดแอปและมี Token อยู่ใน Persistent Storage นั่นเอง

// Firebase Messaging Service
override fun onNewToken(token: String) {
    saveNewTokenToLocalStorage(token)
    
    val workRequest = /* ... */
    WorkManager
        .getInstance(/* ... */)
        .enqueue(workRequest)
}

// Application
if (isTokenInLocalStorageAvailable()) {
    val workRequest = /* ... */
    WorkManager
        .getInstance(/* ... */)
        .enqueue(workRequest)
}

ด้วยการทำงานของ Work Manager จะทำให้การส่ง Token ให้กับ Web Service ยังคงสามารถทำต่อไปได้เรื่อย ๆ เมื่อเกิดปัญหา​โดยไม่จำเป็นต้องรอให้เปิดแอปขึ้นมาก่อน ช่วยลด Gap ที่ทำให้ Web Service ส่ง Push Notification ไม่ได้ให้น้อยลงนั่นเอง

และสำหรับ Worst-case Scenario อย่างกรณีที่ Work Manager หยุดทำงานกระทันหัน (เช่น ปิดและเปิดเครื่องใหม่) ก็จะกลับมาทำงานใหม่อีกครั้งตอนที่แอปเริ่มทำงานจนกว่าจะส่ง Token ได้

ใช้วิธีไหนดีที่สุด?

เมื่อพิจารณาจากทั้ง 3 วิธีที่เจ้าของบล็อกหยิบมาเล่าในบทความนี้ก็จะเห็นว่าวิธีที่ 3 นั้นดูเหมือนจะมีประสิทธิภาพมากที่สุด เพราะใช้ประโยชน์จาก Work Manager เพื่อลด Gap ที่รอส่ง Token ให้ Web Service

แต่ทว่าวิธีที่ 2 ก็ไม่ได้แย่แต่อย่างใด และเป็นวิธีที่สามารถใช้งานได้จริง เพราะทำความเข้าใจได้ง่ายกว่าวิธีที่ 3 อีกทั้งโอกาสที่ Token จะส่งหา Web Service ไม่สำเร็จนั้นมีโอกาสเกิดขึ้นได้น้อยมาก และต่อให้เกิดปัญหาดังกล่าวก็อาจจะไม่ได้ร้ายแรงอะไรมากนักถ้าแอปไม่ได้ส่ง Push Notification ที่สำคัญมากในขณะนั้น

ดังนั้นจะใช้วิธีที่ 2 หรือวิธีที่ 3 ก็เลือกได้ตามใจชอบเลย แค่อย่าทำแบบวิธีที่ 1 ก็พอ