แจ้งให้ user รู้ว่าแอปมี version ใหม่ด้วย in-app updates

Android Sep 23, 2019

เคยไหม ที่โดน user บ่นใส่ทีมคอมมูนิตี้ซึ่งมีหน้าที่ดูแล user ในแอพที่เรา develop อยู่ ว่าแอพอัพเดตแล้วทำไมไม่บอก มาลวงมาหลอกฉันเล่นทำไม เดี๋ยวๆ

ภาพรวมทั้งหมดจ้า แต่ไม่ได้หยิบแอพ Anna มาใส่เพราะเราทดสอบกับแอพบล็อกตัวเองก่อนค่อยลงไปใส่ใน Anna จ้า

คือแอพที่เราดูแลอยู่คือแอพ Anna แล้ว user ชอบบ่นว่าทำไมพี่เลี่ยน อัพแอพ version ใหม่เลยไม่บอกเขาเลย เราเลยคิดว่าการใช้ in-app updates เป็นการแก้ปัญหาโดยที่เราช่วยฝั่งคอมมูนิตี้อีกแรงนึง

แอพ Anna จะมีตัวละครในแอพและในเพจ 3 ตัวด้วยกันคือ 
พี่เลี่ยน เป็นเอเลี่ยนชื่อแอนนาวัน 
ณ๊องค์ เป็นเอเลี่ยนเหมือนแอนนาวัน ชื่อแอนนาทู 
และแป๊ะก๊วยคืออาเจ๊กคนหนึ่ง
ซึ่งเพจ Anna คนที่มาสื่อสารกับลูกเพจหรือ user ก็คือเจ้าแอนนาวัน หรือพี่เลี่ยนนั่นเอง

ก่อนหน้านี้ทีมเราจะใช้ยิงจากหลังบ้าน เพื่อบอกว่า มีแอพ version ใหม่บน store มาแล้วนะ ซึ่งจะมีเคสแบบต้องอัพเดต ไม่งั้นแอพพัง เอ้ยยย มีการปรับเปลี่ยนบางอย่างทีจำเป็นกับ user กับไม่ต้องอัพเดตก็ได้ แล้วขึ้น dialog ออกมา หน้าบ้านแบบเราต้องไปบอกทีมหลังบ้าน ตอนที่เรา check เองแล้วว่าแอพ version ใหม่นั้น ขึ้น store เรียบร้อยแล้วนะ ไหนจะต้องบอกคอมมูเพื่อให้เขาบอก user ว่าพี่เลี่ยนมีอัพเดตแล้วนะ

เราเห็นแอพอะดวงทำ เอ้ออออ มันดีย์อ่ะ คือ user เปิดแอพมารู้เลย ว่ามีอัพเดตจาก store แล้วนะ

แล้วสิ่งนี้ที่ช่วยเราแก้ปัญหานี้คืออะไรหล่ะ?

จากบล็อกนี้ของพี่ยู เราจึงได้รู้ว่าเจ้าสิ่งนี้เรียกว่า in-app updates นั่นเองงงง

Android In-App Update
สิ่งที่รอคอยมานาน เมื่อเดือนพฤษภา 2019 Google ได้ปล่อย API ให้ Developer สามารถทำ Update App ในระหว่างการใช้งาน App ที่เรา Dev กันอยู่ได้ โดยวิธีการเรียก Update จะแบ่งเป็น 2 mode…

ส่วนอันนี้ document

Support in-app updates | Android Developers
Keeping your app up-to-date on your users' devices enables them to try new features, as well as benefit

ปล. ตอนที่กำลังเขียนบล็อกนี้ยังไม่ได้อยู่บน production นะเออ แค่ทดลองก่อนจ้า (ตอนที่ได้อ่านบล็อกน่าจะขึ้น production จริงไปแล้วก็เป็นได้ 555) แต่ app bundle คือของจริง

สรุปสั้นๆจาก document นะ เกี่ยวกับเจ้า in-app updates สิ่งที่ต้องเตรียมมีดังนี้

  • device ใช้ได้ตั้งแต่ Android 5.0 (API level 21) ขึ้นไป
  • และใช้ Play Core library 1.5.0 หรือสูงกว่า

หน้าตามันมี 2 แบบ คือ Flexible กับ Immediate

แบบ Flexible มันจะขึ้นเป็น dialog ออกมา ซึ่งเราจะอัพก็ได้ ไม่อัพก็ได้ และมันจะแอบ download แอพ version ใหม่ใน background

ส่วนแบบ Immediate มันจะขึ้นเต็มจอ และบังคับอัพเดตกลายๆ (เพราะกดปุ่มปิดได้ งงม่ะ) ดังนั้นเราจะไม่สามารถใช้แอพได้เมื่อแอพกำลัง download version ใหม่อยู่

ก่อนที่จะทำเจ้า in-app updates ได้นั้น แอพของเราจะต้องเป็น Android App Bundles เท่านั้น มันไม่ support apk เน้ออออออ ดู document สิ

ถ้ายังไม่ได้ทำ Android App Bundles อ่านก่อนที่นี่เลยจ้า

ทดลองทำ Android App Bundles ลง Play Store เพื่อแอปที่เบากว่า
ก่อนอื่นนั้น แอปเราต้องเป็น API level 28 แล้วนะ เพื่อความปลอดภัยและประสิทธิภาพที่ดีขึ้น ซึ่งแอปใหม่ก็ต้องเป็น 28 ตั้งแต่เดือนสิงหาคม ส่วนแอปที่มีอยู่ใน store นั้นต้องอัพเดตก่อนเดือนกันยายนนะเออ…

มาเริ่มเขียนโค้ดกันเถอะ~~

ก่อนอื่น ไปเพิ่มที่ dependency ใน module ของเราก่อนนะ

dependencies {
    implementation 'com.google.android.play:core:1.6.1'
}

Play Core Library release notes | Android Developers
When you publish your app with an Android App Bundle, Google Play's Dynamic Delivery allows your app to download…developer.android.com

เมื่อ sync gradle เรียบร้อยแล้ว เรามา init AppUpdateManagerFactory

val appUpdateManager = AppUpdateManagerFactory.create(this)

เราเลือกค่า appUpdateInfo เพื่อนำไป check ว่า version ใหม่มายังอ่ะเลี่ยน (ซึ่งเลี่ยนบอกไปถาม Play Store มาอีกที)

val appUpdateInfoTask = appUpdateManager.appUpdateInfo

จากนั้นเพิ่ม listener ไว้เพื่อ check ว่า ถ้า status เป็น update แล้วน้าน ให้ทำสิ่งใดต่อไป ซึ่งตัว isUpdateTypeAllowed นั้น เราสามารถ check ได้ว่า เป็นการ update แบบ FLEXIBLE หรือ IMMEDIATE

appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // TODO: Request the update.
    }
}

ในเมื่อมันเลือกได้ว่าอัพเดตแบบไหน เราต้องไปสั่งตรงไหนนะ

ใน TODO นั้น เราใส่เพิ่มว่า ถ้าเป็น condition นี้น้านนน จะให้ทำอัลไลต่อ เช่น เมื่อ
แอพมีอัพเดต และถ้าบังคับอัพเดตเป็น IMMEDIATE ซึ่งตรง parameter ที่สองนั้น ใส่ให้ตรงกับ condition เราด้วยเน้อ

appUpdateManager.startUpdateFlowForResult(
    appUpdateInfo,
    AppUpdateType.IMMEDIATE,
    this,
    MY_REQUEST_CODE)
เรียกง่ายๆ ลอกนี่ไปใช้ได้เลย เปลี่ยนแค่ type จ้า

สรุปโค้ดจะเป็นแบบนี้จ้า

เราทดลองกับ Internal Test แอพของเราเอง ว่าใส่แล้วหน้าตาเป็นอย่างไร ซึ่งความงงก็คือ

  • ตอนทดสอบ เราลด versionCode ให้น้อยกว่าบน Internal Test ไม่งั้นมันไม่เข้า condition หน้าต่างของหัวใจมันจะไม่มาจ้า ส่วน versionName ไม่มีผลจ้า
  • อันนี้ที่เราลองใส่ทั้ง FLEXIBLE หรือ IMMEDIATE แล้ว มันก็เป็น true ทั้งคู่เว้ย อ่ะโผล่มาทั้งสองแบบตาม condition งงไหม งง และแน่นอนว่า เราต้องเลือกเพียงอย่างเดียวหว่ะ ว่าจะเอาแบบไหนแน่ เพราะใส่ condition ทั้งสองแบบแล้วมันไม่ขึ้นเลย

ซึ่งจริงๆ ถือว่าเราทำหน้าที่ได้อย่างเรียบร้อย ในการให้หน้าต่างมันขึ้นมา แต่ๆๆๆๆ ยังไม่จบจ้า อย่าลืมว่า กด update แล้ว ยังไงต่อนะ

Update App แบบ Flexible

ในที่นี้จะอธิบายแบบ FLEXIBLE ต่อแล้วกันเนอะ ไม่ว่าจะเป็น เคส user ซื่อๆกด update หรือ เคส user ขี้เกียจ ยังก่อนเดี๋ยวมาอัพ เราต้องมา handle ต่อที่ onActivityResult จ้า ดังนี้

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == MY_REQUEST_CODE) {
        when (resultCode) {
            Activity.RESULT_OK -> {
                popupSnackbarForState("App is uploading", Snackbar.LENGTH_INDEFINITE)
            }
            Activity.RESULT_CANCELED -> {
                popupSnackbarForState("You cancel for update new version.", Snackbar.LENGTH_SHORT)
            }
            ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> {
                popupSnackbarForState("App download failed.", Snackbar.LENGTH_SHORT)
            }
        }
    }
}
  • Activity.RESULT_OK สำหรับเคสของ user คนซื่อ เราจะให้ขึ้น snackbar บอกว่าแอพกำลังอัพเดตอยู่
  • Activity.RESULT_CANCELED สำหรับเคสของ user คนขี้เกียจ เราจะขึ้นบอกเพียงสั้นๆว่า แกไม่อัพแอพชั้นเนอะ 55
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED สำหรับเคสของ user คนซื่อ กด download app แล้ว ดั๊นนนนนนนน อัพเดตไม่ได้ ก็ส่งไปบอกเขาสั้นๆซะหน่อย

ถ้า user คนซื่อ download แอพเสร็จแล้วทำไรต่อง่ะ

ก่อนอื่นเราต้องไปสร้าง listener ให้กับเจ้า appUpdateManager กันก่อน ซึ่งเราก็ควรขยับการประกาศเจ้า appUpdateManager ไปเป็น global ซะ เพราะจะเอาไปใช้ต่อ

appUpdateManager.registerListener(this)

เมื่อ register เสร็จแล้วพบว่าตรง this ยัง error อยู่ ดังนั้นเราจึงต้อง implement InstallStateUpdatedListener เพิ่มจ้า

หลังจากเพิ่มปุ๊ปก็ override function ปั๊ป หน้าตาเป็นแบบนี้

override fun onStateUpdate(state: InstallState) {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        popupSnackbarForCompleteUpdate()
    }
}

การทำงาน ถ้า install status คือ download แอพอัพเดตเสร็จแล้ว จะให้ทำอะไรต่อ ในที่นี้คือขึ้น snackbar ที่มี action เพื่อบอก user ว่าแอพอัพเสร็จแล้วนะ จะ restart app ไหม

private fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
            findViewById(R.id.rootview),
            "An update has just been downloaded.",
            Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") {
            appUpdateManager.completeUpdate()
            appUpdateManager.unregisterListener(this@MainActivity)
        }
        show()
    }
}

ถ้า user จะ restart app หลังจากกด action ที่ snackbar แล้วนั้น เพื่อกด restart แอพมันจะจัดการ install แอพใหม่จากบน Play Store ให้เลย และ unregister ที่เราใส่ไว้เมื่อกี้ จบปิ้ง

สรุปโค้ดรวมในรอบนี้

Update App แบบ Immediate

เราสามารถ handle คำสั่งนี้ appUpdateManager.completeUpdate() ได้ทั้งการทำงาน Background คือแอบทำอยู่เงียบๆ แบบโค้ดทางด้านบน และ Foreground คือโชว์ออกมาให้เราเห็นโดยใส่เข้าไปใน onResume() ดังนี้

override fun onResume() {
    super.onResume()
    appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
        if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
            popupSnackbarForCompleteUpdate() 
        }

        try {
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                        appUpdateInfo,
                        AppUpdateType.IMMEDIATE,
                        this,
                        MY_REQUEST_CODE)
            }
        } catch (e: IntentSender.SendIntentException) {
            e.printStackTrace()
        }
    }
}

มันจะขึ้นหน้าตา download app ให้เราดูเลย หลังจากนั้น install เหมือนแบบ Flexible จากนั้น restart แอพใหม่ เป็นอันจบพิธี

สรุปโค้ดรอบสุดท้าย

เมื่อใส่โค้ดทั้งสองอย่างถูกต้องแล้ว จะประสบปัญหา หลังจาก restart กลับมาใหม่ พบว่าแอพเปิดซ้อนกัน และขึ้นให้ฉัน update อยู่หว่ะ ทำไงต่อไปดี มันบัคๆอยู่เพราะทดสอบกับ Internal Test หรอ ;__;

ข้อควรระวังในการทดสอบ

  • ปรับ version code ให้ตํ่ากว่าตัวที่อยู่ใน store (ส่วน version name แล้วแต่ดุลยพินิจและจักรยาน เอ้ยยย วิจารณญาณของท่าน)
  • ตอนที่เรา build บนเครื่องเพื่อ test นั้น ต้องปรับ Build Variants ให้ตรงกับที่เรา upload app ลง play store นะ ไม่งั้นตอน download และ install เสร็จมันเด้งหน้า Activity ซ้อน และเด้งหน้าตา update ให้เราดูต่างหน้าอีก อยู่ในวังวนแห่งการ update app อยู่รํ่าไป

การนำไปปรับใช้

เราไปเอาปรับใช้คล้ายๆทีมจอย คือ ของเดิมคือถ้า user จะ download แอพแล้ว ก็จะพาไปหน้า Play Store เพื่อกด update app แต่เปลี่ยนเป็น update in-app update เป็นแบบ Immediate แทน

ก่อนจบบล็อกสรุปกันนิดๆหน่อยๆ

ทริคเล็กน้อย ไม่ว่าจะเป็นแบบ IMMEDIATE หรือ FLEXIBLE มันจะตรวจเองเลยว่า device นั้นต่อ WiFi หรือยัง ถ้ายังจะมีถามด้วยนะว่าจะต่อ WiFi หรือจะโหลดเลย

ทั้งหมดทั้งมวลนั้น ขอขอบคุณพี่ยู u naja ในการช่วย solve ปัญหาการ update install แล้วแอพเด้งขึ้นมาให้ update อีกแล้วด้วยมา ณ ที่นี้ค่าาา ~~ และขอบคุณชาวแว่นดำที่ตรวจให้บล็อกให้เราด้วยจ้า นานหน่อยแต่คนอ่านน่าจะได้สาระกลับไปแน่นอน

Reference:

Android In-App Updates — Common pitfalls and some good patterns
Google released the In-app updates feature in which the apps can nudge the users to update their apps without even going to the play store. If an update is available, the users will see a dialog or…
Exploring in-app updates on Android
I’m sure there has often been a time when you’ve needed to send out an app update that has some form of urgency — maybe there’s a security issue or some bug which is causing a lot of issues for…
How to work with Android’s in-app update API?
I recently come across a new kind of app update flow which may have provided by Google Play. I liked the seamless flow to update an Android application. I observed below-mentioned steps in Hotstar ...

สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

โพสต์โดย MikkiPastel เมื่อ วันอาทิตย์ที่ 10 ธันวาคม 2017

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.