Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Introduction to Android In-app updates [ja]
Search
Daichi Furiya (Wasabeef)
May 17, 2019
Programming
5
730
Introduction to Android In-app updates [ja]
Introduction to Android In-app updates [ja]
Daichi Furiya (Wasabeef)
May 17, 2019
Tweet
Share
More Decks by Daichi Furiya (Wasabeef)
See All by Daichi Furiya (Wasabeef)
DevFest Tokyo 2025 - Flutter のアプリアーキテクチャ現在地点
wasabeef
6
2.3k
About Flutter Architecture
wasabeef
1
290
2023 Flutter/Dart Summary
wasabeef
0
98
I/O Extended 2023 - Dart と Flutter の新機能
wasabeef
0
200
I/O Extended 2023 - Flutter 活用事例
wasabeef
10
3k
What it Takes to be a Flutter Developer
wasabeef
0
210
FlutterKaigi 2022 Keynote
wasabeef
1
650
Flutter Hooks を使ったアプリ開発 / App Development with the Flutter Hooks
wasabeef
2
1.5k
Flutter 2021 の振り返りと今後のアプリ開発に向けて / Looking back on Flutter 2021 and for future app development.
wasabeef
4
2.2k
Other Decks in Programming
See All in Programming
AIエージェントを活かすPM術 AI駆動開発の現場から
gyuta
0
420
配送計画の均等化機能を提供する取り組みについて(⽩⾦鉱業 Meetup Vol.21@六本⽊(数理最適化編))
izu_nori
0
150
JETLS.jl ─ A New Language Server for Julia
abap34
1
400
AIエンジニアリングのご紹介 / Introduction to AI Engineering
rkaga
8
2.7k
dotfiles 式年遷宮 令和最新版
masawada
1
770
ローターアクトEクラブ アメリカンナイト:川端 柚菜 氏(Japan O.K. ローターアクトEクラブ 会長):2720 Japan O.K. ロータリーEクラブ2025年12月1日卓話
2720japanoke
0
730
宅宅自以為的浪漫:跟 AI 一起為自己辦的研討會寫一個售票系統
eddie
0
500
SwiftUIで本格音ゲー実装してみた
hypebeans
0
370
MAP, Jigsaw, Code Golf 振り返り会 by 関東Kaggler会|Jigsaw 15th Solution
hasibirok0
0
240
C-Shared Buildで突破するAI Agent バックテストの壁
po3rin
0
390
Building AI Agents with TypeScript #TSKaigiHokuriku
izumin5210
6
1.3k
UIデザインに役立つ 2025年の最新CSS / The Latest CSS for UI Design 2025
clockmaker
18
7.5k
Featured
See All Featured
The Language of Interfaces
destraynor
162
25k
Git: the NoSQL Database
bkeepers
PRO
432
66k
Designing for Performance
lara
610
69k
Optimizing for Happiness
mojombo
379
70k
The Invisible Side of Design
smashingmag
302
51k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.3k
Practical Orchestrator
shlominoach
190
11k
Automating Front-end Workflow
addyosmani
1371
200k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How STYLIGHT went responsive
nonsquared
100
6k
Speed Design
sergeychernyshev
33
1.4k
Transcript
In-app updates Wasabeef Shibuya.apk #34
About me Daichi Furiya Google Developers Expert CATS, CyberAgent @wasabeef_jp
wasabeef
In-app updates
Ref: Developer Keynote
In-app updates で出来ること
In-app updates で出来ること Type = FLEXIBLE DL 中に UI の操作が可能
好きなタイミングでインス トール・再起動 Wi-Fi かどうかを考慮
In-app updates で出来ること Type = IMMEDIATE DL から インストール・再起 動まで
UI 操作を防ぐことが 出来る Wi-Fi かどうかを考慮
流れを知る
FLEXIBLE
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
FLEXIBLE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
IMMEDIATE
IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
IMMEDIATE の流れを知ろう https://joebirch.co/2019/05/07/exploring-in-app-updates-on-android/
Code
Android 5.0+ (API 21+) Play Core Library 1.5.0+ Require
Gradle dependencies { implementation 'com.google.android.play:core:1.5.0' }
まず訴求を出すには?
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) } } 更新リクエストをする前に、ストアにアプリの最新バージョンがあるか確認します
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 のマネージャクラスのインスタンスを生成します
Task<AppUpdateInfo>#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> にリスナーを登録 AppUpdateInfo には更新するべきかを判断出来る情報が格納されています
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() でストアにアップデートがあるか確認します
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 は少し特殊なので後述します
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
FLEXIBLE IMMEDIATE
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 で更新訴求を表示し、更新フローを開始します
startUpdateFlowForResult(…) appUpdateManager.startUpdateFlowForResult( // addOnSuccessListener で得られる AppUpdateInfo を指定 appUpdateInfo, // FLEXIBLE
または IMMEDIATE AppUpdateType.IMMEDIATE, // アップデートのリクエストをする Activity this, // onActivityResult() で使うための Request Code を指定でき、キャンセルなどの判断をする REQUEST_CODE_UPDATE ) startUpdateFlowForResult で更新方法の指定や onActivityResult で使うためのリクエストコードを指定します
FLEXIBLE IMMEDIATE キャンセル可能
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) } }
FLEXIBLE 実装方法・注意点
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 で更新訴求を表示し、更新フローを開始します
FLEXIBLE 更新フローの 状態を知るには?
Second val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus())
{ DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) AppUpdateManager に更新フローの状態を監視するためのリスナーを登録します FLEXIBLE タイプで更新をする場合には必要になります
InstallStateUpdatedListener val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus())
{ DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) 後々に、unregisterListener でリスナー解除をするために、この例では変数にします
InstallStatus val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus())
{ DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) ダウンロード完了(DOWNLOADED)した場合には、Snackbar などの UI で更新を促します
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 の判断は必要になります
registerListener && unregisterListener val listener = InstallStateUpdatedListener { installState ->
when (installState.installStatus()) { DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // manager.registerListener(listener) // manager.unregisterListener(listener) 次に、どのタイミングでリスナーの登録・解除をするか説明していきます 次に、どのタイミングでリスナーの登録・解除をするか説明していきます
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) } } リスナーの登録は更新フロー開始のところで登録します 解除は後述
Snackbar を表示して 更新を促す
Snackbar val listener = InstallStateUpdatedListener { installState -> when (installState.installStatus())
{ DOWNLOADING -> println("downloading... ") DOWNLOADED -> popupSnackbarForCompleteUpdate() } } // appUpdateManager.registerListener(listener) // appUpdateManager.unregisterListener(listener) 先程説明した InstallStatus で判断し、Snackbar で更新を促します
Snackbar private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply {
setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() } この例では Snackbar のアクションで新しいバージョンのインストールをします
Snackbar#setAction private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply {
setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() }
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 をバックグラウンドで呼び出した場合は、サイレントインストールされます
appUpdateManager.unregisterListener(listener) private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply {
setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() } ここで、リスナーの解除をします
Third fin. private fun popupSnackbarForCompleteUpdate() { Snackbar.make(findViewById(R.id.main), "DL 完了しました。”,LENGTH_INDEFINITE) .apply
{ setAction("更新") { appUpdateManager.completeUpdate() appUpdateManager.unregisterListener(listener) } }.show() }
更新せずに アプリを終了したら?
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) } } 最初に説明した更新があるかどうかだけでは少し不十分のため説明をしていきます
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 状態の場合、ユーザに更新を促す必要があります 更新が完了するまで、そのデータはデバイスストレージに保存されたままとなります
IMMEDIATE 実装方法・注意点
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 で更新訴求を表示し、更新フローを開始します
更新が完了せずに アプリを終了したら?
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) } }
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 で更新フローを再開します
Conclusion..
簡単 ストアを開かずにアップデート出来る 回線状況も考慮されている ただし、強制アップデートではない ※Firebase などサーバで制御は必要 Conclusion
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
Image Resources Photos: - https://unsplash.com - https://www.pexels.com Illustrations: - http://www.chojugiga.com
- https://www.irasutoya.com
twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef