Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
710
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
5
2k
About Flutter Architecture
wasabeef
1
260
2023 Flutter/Dart Summary
wasabeef
0
81
I/O Extended 2023 - Dart と Flutter の新機能
wasabeef
0
190
I/O Extended 2023 - Flutter 活用事例
wasabeef
10
3k
What it Takes to be a Flutter Developer
wasabeef
0
200
FlutterKaigi 2022 Keynote
wasabeef
1
610
Flutter Hooks を使ったアプリ開発 / App Development with the Flutter Hooks
wasabeef
2
1.4k
Flutter 2021 の振り返りと今後のアプリ開発に向けて / Looking back on Flutter 2021 and for future app development.
wasabeef
4
2.2k
Other Decks in Programming
See All in Programming
オープンセミナー2025@広島「君はどこで動かすか?」アンケート結果
satoshi256kbyte
0
150
Bedrock AgentCore ObservabilityによるAIエージェントの運用
licux
9
710
学習を成果に繋げるための個人開発の考え方 〜 「学習のための個人開発」のすすめ / personal project for leaning
panda_program
1
100
LLMOpsのパフォーマンスを支える技術と現場で実践した改善
po3rin
8
960
Webinar: AI-Powered Development: Transformiere deinen Workflow mit Coding Tools und MCP Servern
danielsogl
0
140
コンテキストエンジニアリングで変わるAI活用 リファクタリングワークフローの実践から学んだ形式知
leveragestech
0
100
画像コンペでのベースラインモデルの育て方
tattaka
3
1.8k
CLI ツールを Go ライブラリ として再実装する理由 / Why reimplement a CLI tool as a Go library
ktr_0731
3
1.1k
【第4回】関東Kaggler会「Kaggleは執筆に役立つ」
mipypf
0
650
Claude Code と OpenAI o3 で メタデータ情報を作る
laket
0
130
Flutterと Vibe Coding で個人開発!
hyshu
1
260
実践 Dev Containers × Claude Code
touyu
1
220
Featured
See All Featured
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
229
22k
Facilitating Awesome Meetings
lara
55
6.5k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
Done Done
chrislema
185
16k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
283
13k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
183
54k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
50
5.5k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
161
15k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
Being A Developer After 40
akosma
90
590k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
1k
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