Blog株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。updatesを使って最高のアップデート体験を実現するこんにちは。Androidエンジニアのsyarihuです。ME」では、日々たくさんのアップデートを行っています。新しい機能の提供であったり、不具合の修正など、アップデートの内容はさまざまです。ME」の場合は、Playストアからアプリを配信しています。アプリのアップデートを受け取るためにはPlayストアからの自動アップデートを待つ、あるいは自動アップデートを有効にしていない場合はユーザーが自らアプリのアップデートがあるかを確認し、アップデートがある場合は明示的にアップデートボタンを押すことでアプリのアップデートをユーザーのAndroid端末に反映します。新機能の提供や致命的な不具合の修正などのアップデートはアプリの提供者からするとなるべく早めにアップデートをしてほしいところですが、すべてのユーザーがアップデートをしてくれるとは限りません。そもそもアップデートに気づいていなかったり、Playストアにいってアップデートボタンを押してアップデートが完了するのを待つのが手間でアップデートをする気が起きないなど、アップデートをしないのはユーザーによってさまざまな理由が考えられます。updatesという仕組みがあります。updatesを利用することで、アプリ内でアップデートを検知してユーザーにアップデートを促したり、バックグラウンドでアップデートのダウンロードを行うなど、柔軟にアップデートを提供できます。updatesの検証方法について解説します。updatesとは、Googleが公式に提供するアプリ内アップデートの仕組みです。1.5.0以上を導入することで利用できます。5.0以上のデバイスで利用できます。updatesにはFlexibleとImmediateの2種類のアップデート方式があります。Flexibleはバックグラウンドでダウンロードを行い、ユーザーが任意のタイミングでアップデートを反映する方式です。ダウンロード中は引き続きアプリの使用ができます。Immediateは全画面でアップデート画面を表示してアップデートが終了したら即時反映する方式です。それぞれのアップデート方式について詳しく見ていきましょう。FlexibleアップデートFlexibleアップデートは、バックグラウンドでダウンロードやインストール、インストール状態の監視などを行うアップデート方式です。ダウンロード中は引き続きアプリの使用ができます。updatesの公式ドキュメントより引用)アプリはユーザーに対してアップデートがあることを通知します。Playがバックグラウンドでアップデートデータのダウンロードを行います。アップデートデータのダウンロードを行っている間はユーザーはアプリを使い続けられます。ダウンロードが終わるとアプリはユーザーに対してアップデートを行う準備ができたことを通知します。Playがアップデートを反映して自動的にアプリを再起動します。ユーザーの任意のタイミングでアップデートを提供できる仕組みのため、これは重要ではないアップデートを提供する場合に適しています。ImmediateアップデートImmediateアップデートは、ユーザーに対して全画面でアップデート画面を表示するアップデート方式です。updatesの公式ドキュメントより引用)Flexibleアップデートとは違い、Immediateアップデートはアップデートが終わるまではユーザーはアプリを利用することはできないため、重要なアップデートを提供する場合に適しています。Playにアプリの更新を確認し、更新がある場合はアップデート画面を表示するような実装になるため、アップデートするまで次に進めないようにすることで強制アップデートを実現できます。API経由で行う場合のみ、アプリ内アップデートのPriority(優先度)を設定できます。重要なセキュリティアップデートを行う場合には高いPriorityを設定し、高いPriorityの場合にはImmediateを使って強制アップデートを促すこともできます。ME」では、インストール直後の新規ユーザーと利用中の既存ユーザーでアップデート方式を使い分けるようにしました。Flexibleアップデート既存ユーザーに対してはFlexibleアップデートによるアプリ内アップデートを提供しています。どのようにアプリ内アップデートが行えるのか順番に見ていきましょう。はじめにアップデートの通知です。アップデートにすぐに気づけるように、ホームの一番上にアップデートの通知を表示します。updatesのFlexibleアップデートの画面を表示します。更新ボタンを押すと、アップデートのダウンロードを開始します。ダウンロード中はアプリを継続して利用できます。アップデートのダウンロードが完了すると、ダウンロードの完了を通知します。ユーザーが通知をタップすると、アップデートのインストールを実行します。Playの画面が全画面で表示されます。この間はアプリを利用できませんが、ダウンロードが既に終わっているためインストールはすぐに終わります。インストールが終わるとアプリが自動で再起動されるので、以降は通常通りアプリを利用できます。これを実際に動かしてみると次のようになります。ユーザーに邪魔にならない形で通知を行い、ダウンロードからインストールまですべてアプリ内で行えるので、ストアに遷移することなくスムーズにアプリのアップデートを提供できるようになりました。Immediateアップデートアプリをインストールするだけして、しばらく起動していないユーザーもいます。いったユーザーに対してはログインする前に重要なアップデートがあれば、Immediateアップデートを使ってアップデートを促すようにしています。こちらもどのようにアプリ内アップデートを提供しているのか順番に見てみましょう。ユーザーが規約に同意して始めようとした場合に、アップデートを促すダイアログを表示します。updatesのImmediateアップデートの画面を表示します。右上のボタンから更新をやめることもできますが、こちらは閉じても更新するまで先に進めないようにして強制アップデートを実現しています。実際に動かしてみると次のようになります。こちらもPlayストアに遷移せずにアップデートを実現できるため、通常のアップデートよりはスムーズにアップデートができるようになっています。updatesの実装をViewModel内にまとめて、View側ではLiveDataで状態を監視してUIに反映するだけでアプリ内アップデートを提供できるようになります。ここでは、AACを利用してアプリ内アップデートを提供する実装例を紹介します。Libraryの1.5.0以上が必要です。Libraryのリリースノートからライブラリの最新バージョンを確認して依存関係に追加します(ここでは2020年8月時点での最新バージョンである1.8.0を導入します)。"com.google.android.play:core:1.8.0"}AACのLiveDataとViewModelを利用するため、lifecycle-livedataとlifecycle-viewmodelもリリースノートから最新バージョンを確認して依存関係に追加します(ここでは2020年7月時点の最新バージョンである2.2.0を導入します)。updatesには、ダウンロードやインストールに関するさまざまな状態があります。状態をLiveDataで通知できるようにするため、各状態をあらかじめ定義します。updatesのアップデート情報を取得した際に、アップデート情報はAppUpdateInfoクラスで返却されます。AppUpdateInfoはアプリ内アップデートを開始する際に必要です。classで定義します。updatesからのコールバックを受け取り、状態をLiveDataに反映する処理を実装します。updatesに関する情報を取得するには、AppUpdateManagerクラスを使います。Contextが必要です。ViewModelの引数からApplicationを取得してAppUpdateManagerのインスタンスを取得するには次のようにします。AppUpdateManagerFactory.create(application)}AppUpdateManagerのインスタンスが取得できたら、これを使ってアップデート情報の取得などを行います。アップデート情報を取得するアプリのアップデートがあるかの判定や、現在アップデートの途中なのかなどのアップデート情報を取得するには、AppUpdateManager#getAppUpdateInfo()を使います。AppUpdateManager#getAppUpdateInfo()は次のように実装します。}}アプリがフォアグラウンドにきたときにアップデートを通知できるように、アップデート情報の取得はonResumeで行うのがおすすめです。ViewModel内でonResumeイベントを検知できるように、LifecyleObserverを実装して@OnLifecyleEventを任意のメソッドにつけます。今回はonResumeメソッドを作成して@OnLifecycleEventをつけています。onResumeメソッドの中でAppUpdateManager#getAppUpdateInfo()を呼び出し、アップデート情報を取得します。取得した結果はOnSuccessListenerで受け取ります。取得に成功するとonSuccessメソッドが呼び出されるので、このメソッドの中で結果をLiveDataに通知するなど、必要な処理を行います。インストール状況を取得するインストールを開始したあと、ダウンロード中やインストール中などの状況を監視するにはInstallStateUpdatedListenerを実装します。}}InstallStateUpdatedListenerはAppUpdateManager#registerListenerで登録します。ここではonCreate時に登録しています。使い終わったらListenerの登録の解除を行います。登録の解除にはAppUpdateManager#unregisterListenerを使います。ここではViewModelのonClearedで登録を解除することで、ViewModelが生きている間はイベントが通知されるようにしています。コールバックはonStateUpdateメソッドに通知されるので、このメソッドを使ってインストール状況によって何らかの処理を行います。FlexibleとImmediateを切り替える1つのViewModel内でFlexibleとImmediateの両方に対応するには、いま現在どちらのアップデート方式なのかを保持しておく必要があります。ここでは、ViewModel内にappUpdateTypeという変数を定義してアップデート方式を保持できるようにします。AppUpdateType.FLEXIBLE}アップデート方式は必ず設定してほしいので、カスタムプロパティを使ってappUpdateTypeが初期値のままであれば例外を投げるようにします。アップデート方式の設定はメソッド経由でできるようにそれぞれメソッドを用意します。Playにアップデートが通知されてからの日数を確認できます。Flexibleアップデートをユーザーに通知する前に数日待ってから通知したり、Immediateアップデートにより即時アップデートを行ってほしい重要なアップデートの場合は待たずにすぐに通知するなどの判断をするのに利用できます。Playにアップデートが通知されてから2日待ってユーザーに通知するためには次のように実装します。appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}はじめに、Flexibleアップデート方式でのアップデートが許可されているかを確認します。アップデート許可はAppUpdateInfo#isUpdateTypeAllowed()で確認できます。引数にはAppUpdateType.FLEXIBLEまたはAppUpdateType.IMMEDIATEを渡します。ここではFlexibleアップデート方式でのアップデートが有効かを返却したいので、AppUpdateType.FLEXIBLEを渡します。Playからアップデート通知が届いてからの日数はAppUpdateInfo#clientVersionStalenessDays()で取得できます。このメソッドはアップデートが届いていなければnullが返却されるため、先にnullかどうかを確認してから日数を判定します。今回は2日以上経過していればtrueを返すようにしています。これらの条件はデバッグする際にも指定していると必ずfalseとなってしまうため、デバッグ中であれば必ずtrueを返すようにするなどの対応を入れておくとよいでしょう。APIを使ってアプリをアップロードする際、リリースするアプリのアプリ内アップデートの優先度を設定できます。優先度は0から5までの値を設定することができます。最高の優先順位は5で、デフォルトの優先度は0となっています。アプリ内アップデートの優先度を設定することで、重要なセキュリティアップデートの場合はImmediateアップデートを使ってアプリ内アップデートを行うなどの判断に利用できます。Immediateアップデートで優先度が4以上の場合にユーザーに通知するには次のようにします。appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)}優先度を取得するにはAppUpdateInfo#updatePriority()を使います。ここでは4以上の場合にtrueを返すようにしています。アップデート許可の確認はFlexibleのときと同様で、今回はImmediateの確認をしたいので引数にAppUpdateType.IMMEDIATEを渡します。これで、優先度が高い場合のみImmediateアップデートを有効にする判定ができるようになりました。Publisherを利用している場合は、2.8.0からupdatePriorityの設定ができるようになっています。アプリ内アップデートを提供する際には2.8.0以上にアップデートしておくとよいでしょう。updatesのアップデート状態を通知する処理を実装していきます。classのInAppUpdatesStateを使います。LiveDataでInAppUpdatesStateを監視するため、次のように実装します。_stateViewModel内でのみ状態の変更ができるようにMutableLiveDataはprivateで実装し、状態の監視が外からできるようにLiveDataで外に公開します。次に、取得したアップデート状態をLiveDataに通知する処理を見ていきましょう。アップデート状態の取得は前述したOnSuccessListenerのonSuccessを使って受け取れるので、onSuccessの中身を実装します。}}AppUpdateInfo#updateAvailability()で現在のアップデート状態を取得できます。intで返却されるため、UpdateAvailabilityに定義されている定数によって状態を判別します。各定数は次のように定義されています。各状態ごとの実装を順番に見ていきましょう。アップデート可能な状態のときです。通知可能な状態であればRequestUpdateをpostしてアップデート可能であることを通知します。それ以外の場合はアップデートが無い状態としてUpdateNotAvailableをpostします。Playにアップデートが届いてからの経過日数やアップデートの優先度を元にアップデート方式を変更する場合はそういった処理をアップデート可能な状態のときに行うとよいでしょう。次に、ダウンロードやインストールが開始されているアップデート中の状態のときです。}}アップデート中の場合、ダウンロードやインストールの状況はonStateUpdateのInstallStateクラスから詳しい状況を取得できます。そのため、バックグラウンドでダウンロード処理が走るFlexibleアップデート方式の場合はonStateUpdateを使って状況を通知したほうがよいです。Immediateアップデート方式の場合は継続してアプリを使うことが想定されていないため、ダウンロードやインストール中にアプリに戻ってきてしまった場合にImmediateアップデートの画面に戻せるようにアップデートを再開するRequestResumeをpostしています。最後にアップデートが無い状態の場合です。このときは何も通知する必要がないのでUI側では特に何も表示しなくてもよいでしょう。インストール状況を通知するアプリ内アップデートのインストール状況はInstallStateUpdatedListenerのonStartUpdateを使って取得できます。ダウンロード中やインストール中などの状況を判別するには次のように実装します。}}現在ダウンロード中なのか、インストール中なのかはInstallState#installStatus()からintで取得できます。各状態はInstallStatusの定数によって判別できるので、各状態に応じたInAppUpdatesStateをpostしています。現在ダウンロード済のファイルサイズはInstallState#bytesDownloaded()、ダウンロードするファイルの合計サイズはInstallState#totalBytesToDownload()で取得できます。ダウンロード中の場合はこれらの値も一緒にpostしています。これら2つの値を使うことで、現在何%ダウンロード済なのかを表示する際に利用できます。ダウンロード中以外の場合はこれら2つの値は0が返却されるため、ダウンロード中以外の場合はpostしていません。updatesを開始するには、次のように実装します。)}第一引数のappUpdateInfoにはOnSuccessListenerで取得したAppUpdateInfoクラスのインスタンスを渡します。第二引数のappUpdateTypeにはFlexibleまたはImmediateのどちらのアプリ内アップデートを行うのかをintで指定します。アプリ内アップデート画面に遷移するために必要なActivityと、startActivityForResultに必要なREQUEST_CODEを渡します。これを実行すると、Flexibleの場合はアップデートダイアログ、Immediateの場合は全画面でアップデート画面が表示されます。アプリ内アップデートのActivityから帰ってきたときの処理は次のように実装します。}}requestCodeはstartUpdateFlowForResultに渡したrequestCodeで判別します。RESULT_OKは、Flexibleアップデートの場合はユーザーが更新ボタンを押した後に呼び出され、Immediateの場合はユーザーが更新ボタンを押した後に端末の戻るボタンなどでアプリに戻ってきてしまった場合に返ります。更新開始後にアプリに戻ってきた時点でアップデート中の状態がOnSuccessListenerから取得できるので、RESULT_OKのときに何かをするよりonSuccessで何らかの処理を行うほうがよいでしょう。RESULT_CANCELEDは、Flexibleの場合は「ダウンロードしない」を押したとき、Immediateの場合は右上のバツボタンを押したときに返ります。キャンセルされた場合は数日後に再度表示するなど、アプリの要件によってアプリ内アップデートの再表示を制御したいときなどに利用するとよいでしょう。RESULT_IN_APP_UPDATE_FAILEDはアプリ内アップデートがエラーになった場合に返ります。この場合はエラーになったことをユーザーに通知してあげるとよいでしょう。各状態をLiveDataにpostしているので、これをActivityやFragmentのonActivityResultで呼び出すだけでLiveDataで状態を監視できます。アップデートを反映するFlexibleアップデートの場合は、アップデートデータのダウンロードが完了したあとにアップデートを反映してあげる必要があります。アップデートの反映は次のようにします。appUpdateManager.completeUpdate()}AppUpdateManager#completeUpdate()を呼び出すとインストール画面に遷移してインストールが開始されます。インストールが終わるとアプリが自動で再起動されるので、これを呼び出すだけでアップデートの反映が行われます。UIを実装するViewModelの実装が終わったので、最後にUIを実装していきます。updatesの状態をLiveDataで監視し、状態に応じてユーザーに通知します。ViewModelの初期化まずはViewModelの初期化です。}}LifecycleObserverへの登録とアップデート方式の選択は忘れないように実装しましょう。アップデート状態に応じてUIに反映するViewModelが実装できたらアップデート状態をLiveDataで監視して、状態に応じてUIに反映します。}今回はアップデートのリクエストなどがきたらすぐにアップデート画面を表示するような実装をしていますが、アップデートがある場合は画面にアップデートがあることをユーザーに通知してボタンを押したらアップデート画面を表示したいケースも多いでしょう。そのようなケースではLiveDataをmapして特定の条件になったらUI側に反映するなど、柔軟に実装してあげるとよいでしょう。アップデート結果の受け取りアップデートのキャンセルなどはonActivityResultで受け取るので、忘れないように実装しておきましょう。resultCode)}これで実装はおわりです。Playへの配信が必要な都合上、ベータ版やアルファ版または内部テスト版トラックに配信する必要があります。ベータ版やアルファ版または内部テスト版トラックへの配信は毎回バージョンコードを上げなければならず、デバッグをする際に利用するにはとても非効率です。sharingを使ってテストをするのがよいです。sharingを使って検証するには次の手順でアプリのアップロード、インストールを行います。updatesの画面が表示される重要なのは、5のアップデートがある状態を作る部分です。Playに対して更新があることを伝えています。updatesの検証ができます。updatesの検証方法について解説してきました。updatesを利用することで、Playストアに遷移する手間がなくアプリ内でアプリの更新を行えるため、うまく活用することで自動更新をONにしていないユーザーなどにもアップデートをしてもらえる可能性が高まります。アプリの新機能や不具合修正などをユーザーにいち早く提供するためにも、こういった仕組みを使って最高のアップデート体験を実現していきましょう。--マネーフォワードでは、エンジニアを募集しています。ご応募お待ちしています。09:30Tweet関連記事2021-02-09AndroidアプリにおけるUIの状態保存と復元について調べてみたこんにちは。2020年新卒でAndroidエンジニアの宮本です。読者になるこのブログについてはてなブログをはじめよう!moneyforwardDevさんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。