Slide 1

Slide 1 text

In-app updates Wasabeef Shibuya.apk #34

Slide 2

Slide 2 text

About me Daichi Furiya Google Developers Expert CATS, CyberAgent @wasabeef_jp wasabeef

Slide 3

Slide 3 text

In-app updates

Slide 4

Slide 4 text

Ref: Developer Keynote

Slide 5

Slide 5 text

In-app updates で出来ること

Slide 6

Slide 6 text

In-app updates で出来ること Type = FLEXIBLE DL 中に UI の操作が可能 好きなタイミングでインス トール・再起動 Wi-Fi かどうかを考慮

Slide 7

Slide 7 text

In-app updates で出来ること Type = IMMEDIATE DL から インストール・再起 動まで UI 操作を防ぐことが 出来る Wi-Fi かどうかを考慮

Slide 8

Slide 8 text

流れを知る

Slide 9

Slide 9 text

FLEXIBLE

Slide 10

Slide 10 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 11

Slide 11 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 12

Slide 12 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 13

Slide 13 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 14

Slide 14 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 15

Slide 15 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 16

Slide 16 text

FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 17

Slide 17 text

IMMEDIATE

Slide 18

Slide 18 text

IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 19

Slide 19 text

IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 20

Slide 20 text

IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 21

Slide 21 text

IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/

Slide 22

Slide 22 text

Code

Slide 23

Slide 23 text

Android 5.0+ (API 21+) Play Core Library 1.5.0+ Require

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

まず訴求を出すには?

Slide 26

Slide 26 text

First val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } 更新リクエストをする前に、ストアにアプリの最新バージョンがあるか確認します

Slide 27

Slide 27 text

AppUpdateManager val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } 更新を確認するには In-app updates のマネージャクラスのインスタンスを生成します

Slide 28

Slide 28 text

Task#addOnSuccessListener val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } AppUpdateManager から取得した Task にリスナーを登録 AppUpdateInfo には更新するべきかを判断出来る情報が格納されています

Slide 29

Slide 29 text

appUpdateInfo.updateAvailability() val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } AppUpdateInfo#updateAvailability() でストアにアップデートがあるか確認します

Slide 30

Slide 30 text

InstallStatus.class public @interface UpdateAvailability { int UNKNOWN = 0; int UPDATE_NOT_AVAILABLE = 1; int UPDATE_AVAILABLE = 2; int DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS = 3; } UpdateAvailability はこれらが定義されており DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS は少し特殊なので後述します

Slide 31

Slide 31 text

appUpdateInfo.isUpdateTypeAllowed(type) val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } AppUpdateInfo#isUpdateTypeAllowed で指定タイプが利用できるか確認します FLEXIBLE or IMMEDIATE

Slide 32

Slide 32 text

FLEXIBLE IMMEDIATE

Slide 33

Slide 33 text

startUpdateFlowForResult(…) val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } startUpdateFlowForResult で更新訴求を表示し、更新フローを開始します

Slide 34

Slide 34 text

startUpdateFlowForResult(…) appUpdateManager.startUpdateFlowForResult( // addOnSuccessListener で得られる AppUpdateInfo を指定 appUpdateInfo, // FLEXIBLE または IMMEDIATE AppUpdateType.IMMEDIATE, // アップデートのリクエストをする Activity this, // onActivityResult() で使うための Request Code を指定でき、キャンセルなどの判断をする REQUEST_CODE_UPDATE ) startUpdateFlowForResult で更新方法の指定や onActivityResult で使うためのリクエストコードを指定します

Slide 35

Slide 35 text

FLEXIBLE IMMEDIATE キャンセル可能

Slide 36

Slide 36 text

First fin. val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } }

Slide 37

Slide 37 text

FLEXIBLE 実装方法・注意点

Slide 38

Slide 38 text

startUpdateFlowForResult(…) val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) } } startUpdateFlowForResult で更新訴求を表示し、更新フローを開始します

Slide 39

Slide 39 text

FLEXIBLE 更新フローの 状態を知るには?

Slide 40

Slide 40 text

Second val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) AppUpdateManager に更新フローの状態を監視するためのリスナーを登録します FLEXIBLE タイプで更新をする場合には必要になります

Slide 41

Slide 41 text

InstallStateUpdatedListener val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) 後々に、unregisterListener でリスナー解除をするために、この例では変数にします

Slide 42

Slide 42 text

InstallStatus val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) ダウンロード完了(DOWNLOADED)した場合には、Snackbar などの UI で更新を促します

Slide 43

Slide 43 text

InstallStatus.class public @interface InstallStatus { int UNKNOWN = 0; int REQUIRES_UI_INTENT = 10; int PENDING = 1; int DOWNLOADING = 2; int DOWNLOADED = 11; int INSTALLING = 3; int INSTALLED = 4; int FAILED = 5; int CANCELED = 6; } 最低限 DOWNLOADED の判断は必要になります

Slide 44

Slide 44 text

registerListener && unregisterListener val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // manager.registerListener(listener) // manager.unregisterListener(listener) 次に、どのタイミングでリスナーの登録・解除をするか説明していきます 次に、どのタイミングでリスナーの登録・解除をするか説明していきます

Slide 45

Slide 45 text

registerListener val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) manager.registerListener(listener) } } リスナーの登録は更新フロー開始のところで登録します 解除は後述

Slide 46

Slide 46 text

Snackbar を表示して 更新を促す

Slide 47

Slide 47 text

Snackbar val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) 先程説明した InstallStatus で判断し、Snackbar で更新を促します

Slide 48

Slide 48 text

Snackbar private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply { setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() } この例では Snackbar のアクションで新しいバージョンのインストールをします

Slide 49

Slide 49 text

Snackbar#setAction private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply { setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() }

Slide 50

Slide 50 text

appUpdateManager.completeUpdate() private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply { setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() } AppUpdateManager#completeUpdate() でインストールを実行します completeUpdate をフォアグラウンドで呼び出した場合は、アプリが再起動され completeUpdate をバックグラウンドで呼び出した場合は、サイレントインストールされます

Slide 51

Slide 51 text

appUpdateManager.unregisterListener(listener) private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply { setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() } ここで、リスナーの解除をします

Slide 52

Slide 52 text

Third fin. private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply { setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() }

Slide 53

Slide 53 text

更新せずに アプリを終了したら?

Slide 54

Slide 54 text

DOWNLOADED val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE)) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) manager.registerListener(listener) } } 最初に説明した更新があるかどうかだけでは少し不十分のため説明をしていきます

Slide 55

Slide 55 text

DOWNLOADED val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.installStatus() == DOWNLOADED) { popupSnackbarForCompleteUpdate() } else if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(FLEXIBLE) ) { manager.startUpdateFlowForResult(info, FLEXIBLE, this, REQUEST_CODE_UPDATE) manager.registerListener(listener) } } DOWNLOADED 状態の場合、ユーザに更新を促す必要があります 更新が完了するまで、そのデータはデバイスストレージに保存されたままとなります

Slide 56

Slide 56 text

IMMEDIATE 実装方法・注意点

Slide 57

Slide 57 text

startUpdateFlowForResult(…) val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(IMMEDIATE)) { manager.startUpdateFlowForResult(info, IMMEDIATE, this, REQUEST_CODE_UPDATE) } } startUpdateFlowForResult で更新訴求を表示し、更新フローを開始します

Slide 58

Slide 58 text

更新が完了せずに アプリを終了したら?

Slide 59

Slide 59 text

DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS 最初に説明した更新があるかどうかだけでは少し不十分のため説明をしていきます val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(IMMEDIATE)) { manager.startUpdateFlowForResult(info, IMMEDIATE, this, REQUEST_CODE_UPDATE) } }

Slide 60

Slide 60 text

DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS val manager = AppUpdateManagerFactory.create(this) manager.appUpdateInfo.addOnSuccessListener { info -> if (info.updateAvailability() == DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS || (info.updateAvailability() == UPDATE_AVAILABLE && info.isUpdateTypeAllowed(IMMEDIATE)) ) { manager.startUpdateFlowForResult(info, IMMEDIATE, this, REQUEST_CODE_UPDATE) } } DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS は更新が途中になっている状態であり その場合は startUpdateFlowForResult で更新フローを再開します

Slide 61

Slide 61 text

Conclusion..

Slide 62

Slide 62 text

簡単 ストアを開かずにアップデート出来る 回線状況も考慮されている ただし、強制アップデートではない
 ※Firebase などサーバで制御は必要 Conclusion

Slide 63

Slide 63 text

References: - https://d.android.com/guide/app-bundle/in-app-updates - https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/ Code Resources

Slide 64

Slide 64 text

Image Resources Photos: - https://unsplash.com - https://www.pexels.com Illustrations: - http://www.chojugiga.com - https://www.irasutoya.com

Slide 65

Slide 65 text

twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef