ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの技術部ブログです。主にトクバイ・ロコナビのサービス開発について発信しています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの技術部ブログです。
主にトクバイ・ロコナビのサービス開発について発信しています。

トクバイApp for Androidに「in-app updates」を導入した話

f:id:yokomii:20200522142810p:plain

こんにちは!!
おトクな買い物情報アプリ「トクバイ」のAndroidエンジニアをしている横山です。
リモートワークの利点を最大限生かし、寝巻き姿のままブログを書いてます😪

...

in-app updates API を導入しよう

最近のトクバイアプリAndroid版のアップデート機能として、「in-app updates」をリリースしました。

in-app updates(API)はPlay Core Libraryに含まれている拡張機能で
旧バージョンのアプリを利用しているユーザーに対し、最新バージョンへのアップデートを促し、アプリ内でアップデート処理を実行する機能を提供するAPIです。

トクバイでは、旧バージョンのアプリを利用しているユーザーから不具合のお問い合わせを頂いた際、
不具合の内容にもよりますが、はじめに最新バージョンへのアップデートをお願いしています。

アプリを安定的に利用していただくために、定期的なアップデートは必要不可欠です。
アップデート案内を自動化することで、より多くのユーザーに最高の体験を届けるべく、in-app updatesの導入を行いました。

ImmediateとFlexible

in-app updatesで提供されているアップデート方法は2種類あり、
ユーザー同意後、即座にダウンロードとインストールが実行されるImmediate
バックグラウンドでダウンロードを実行し、完了後にインストール案内を通知するFlexible
のどちらかを状況によって使い分けることができます。


Immediate

f:id:yokomii:20200522142015g:plain:w250

  • アップデート案内を新規画面を立ち上げて表示
  • ユーザー同意後、即座にダウンロード⇨インストールを実行
  • インストール完了後は自動的にアプリを再起動する
  • アップデート処理中にアプリが終了した場合、再起動時に処理を再開する必要がある
  • アップデートが拒否された場合、その後の処理はアプリ側で適切に実装する必要がある
    (ダイアログを表示してアプリを終了など)

利用シーン

  • 致命的なバグが発覚し、半強制的なアップデートが必要なとき
  • APIの仕様変更などの外部要因により、旧バージョンの継続利用ができなくなったとき
  • ユーザー行動を阻害するので、より緊急度の高いときに利用する

Flexible

f:id:yokomii:20200522141622g:plain:w250

  • アップデート案内をダイアログで表示
  • ユーザー同意後、バックグラウンドでダウンロードを開始
  • ダウンロード中もユーザーはアプリを継続利用できる
  • ダウンロード完了後、再度ユーザーに同意を得てからインストールを開始

利用シーン

  • アプリのアップデートを恒常的に案内したいとき
  • 緊急度は低いが安定性を保つため、ユーザーにアップデートを要求したいとき
  • ユーザー行動は阻害しないが、案内を出す頻度は調整すべき
    (ひとつのバージョンにつき一度だけ案内するなど)

トクバイでは定期的なアップデート案内が利用目的だったので、Flexibleアップデートを採用しました。

導入

基本的な導入方法は公式ガイドで詳しく紹介されているのでそちらをご覧ください。
ここでは導入時にハマったポイントや、導入するにあたってのTipsをいくつかご紹介します。

⭐️KTX

開発言語にKotlinを採用している場合、Kotlin拡張機能(KTX)を利用することでコードを簡略化できます。

dependencies {
    implementation 'com.google.android.play:core:1.7.1'
    implementation 'com.google.android.play:core-ktx:1.7.0' // KTX
}
fun update(fragment: Fragment) {
    val activity = fragment.activity ?: return
    manager.appUpdateInfo.addOnSuccessListener { info ->
        if (
            info.updateAvailability() ==
             UpdateAvailability.UPDATE_AVAILABLE
        ) {
            manager.startUpdateFlowForResult(
                info,
                AppUpdateType.IMMEDIATE,
                activity,
                REQUEST_CODE_IMMEDIATE_UPDATE
            )
        }
    }
}

// for KTX
suspend fun updateForKTX(fragment: Fragment) {
    val info = manager.requestAppUpdateInfo()
    if (
        info.updateAvailability() ==
        UpdateAvailability.UPDATE_AVAILABLE
    ) {
        AppUpdateResult.Available(manager, info).startImmediateUpdate(
            fragment,
            REQUEST_CODE_IMMEDIATE_UPDATE
        )
    }
}

⭐️アップデートの中断と再開

アップデート処理中にアプリを終了して再起動した際、アプリ側で適切に再開処理を行う必要があります。

Immediate

suspend fun immediateUpdate(activity: Activity) {
    val info = manager.requestAppUpdateInfo()

    when (info.updateAvailability()) {
        UpdateAvailability.UPDATE_AVAILABLE, // 更新可能(開始時)
        UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS // 更新途中(再開時)
        -> AppUpdateResult.Available(manager, info)
                .startImmediateUpdate(
                    activity,
                    REQUEST_CODE_IMMEDIATE_UPDATE
                )
        UPDATE_NOT_AVAILABLE
        -> Unit
    }
}

アップデート処理中にアプリが終了しても、ダウンロード処理はバックグラウンドで継続実行されるため、アプリ再起動時に案内画面を再表示することでアップデート処理を継続できます。
(ダウンロードが完了していたらインストールが開始されます。)

Flexible

suspend fun flexibleUpdate(activity: Activity) {
    val info = manager.requestAppUpdateInfo()

    // ダウンロード完了済みか否かを判定
    if (info.installStatus() == InstallStatus.DOWNLOADED) {
        // ダウンロードが完了していたらインストール案内を表示
        // popupSnackbarForCompleteUpdate()
        return
    }
    
    AppUpdateResult.Available(manager, info)
        .startFlexibleUpdate(
            activity,
            REQUEST_CODE_FLEXIBLE_UPDATE
        )
}

アップデートに同意した時点で、バックグラウンドでダウンロード処理が開始されます。
アプリ再起動時はinstallStatusをチェックし、ダウンロードが完了していたらインストール案内を通知します。

ref: インストール通知例

⭐️アップデート拒否

Immediate アップデートでユーザーから同意が得られなかった場合、アップデート案内を再度表示したり、その時点でアプリを終了したりすることができます。

Immediate

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == REQUEST_CODE_IMMEDIATE_UPDATE) {
        when (resultCode) {
            Activity.RESULT_CANCELED -> { 
                // アップデート同意前に×ボタンまたはバックキー押下で案内画面が閉じられた時
                finish() // or アップデート再案内
            }
            ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> {
                // 何らかのエラーによってアップデートが行えない時
            }
            Activity.RESULT_OK -> {
                // アップデート同意後、バックキー押下で更新画面が閉じられた時
                finish() // or アプリ終了案内
            }
        }
    }
}

アップデートに同意が得られなかった(画面が閉じられた)時は、ActivityのonActivityResult()が呼ばれます。
アップデート同意後にバックキーで更新画面が閉じた(中断)ときはキャンセル扱いにならず、RESULT_OKが返るので注意が必要です。

⭐バージョンアップ直後の案内を防止する

自動アップデート設定をしているユーザーに配慮し、緊急の場合を除いてバージョンアップ直後の案内は控えるべきです。
そのための仕組みがAPIにも含まれています。

Flexible

suspend fun flexibleUpdate(activity: Activity) {
    val info = manager.requestAppUpdateInfo()
    
    val stalenessDays = info.clientVersionStalenessDays ?: 0
    if (stalenessDays > 4) {
        // TODO アップデート処理
    }
}

AppUpdateInfo.clientVersionStalenessDays で、Play Storeが最新のバージョンアップを検知してからの経過日数を取得できます。
上記の例ではバージョンアップ後、5日以上経過している時にアップデート処理を開始します。

⭐️動作検証

Play Storeからバージョン情報取得するために、
配信中のアプリとアプリケーションIDKeyStoreを一致させる必要があります。
開発環境で異なるアプリケーションIDとKeyStoreを設定している場合は、検証ができないので注意が必要です。

トクバイAppではDebug用と本番用でAppUpdateManagerのインスタンスの生成処理を分離しています。

// in release package
@Module
internal object AppUpdateManagerModule {

    @Singleton
    @Provides
    fun providesAppUpdateManager(
        application: Application
    ): AppUpdateManager = AppUpdateManagerFactory.create(application)
}
// in debug package
@Module
internal object AppUpdateManagerModule {

    @Singleton
    @Provides
    fun providesAppUpdateManager(
        application: Application
    ): AppUpdateManager = FakeAppUpdateManager(application)
}

FakeAppUpdateManagerはUnitテスト用のクラスで、擬似的に動作検証を行うための機能が備わっています。
Play Storeへの更新情報の問い合わせも擬似実行になるので、アプリID不正時のエラーハンドリングを行う必要がありません。

まとめ

ライブラリを入れるだけで簡単に高機能のアップデート案内が出せるため、同様の機能を自前で実装している方や、アップデート機能を新たに追加したい方は導入を検討してみてはいかがでしょうか。
トクバイAppも引き続き、エンジニア主導でガンガンサービス改善をやっていければと思います。


サービス開発&改善にご興味がある方は↓から応募お待ちしております〜🏃‍♀️