Android 앱 인앱 업데이트와 리뷰 요청 구현 — Play In-App Updates, In-App Review API와 버전 관리

Android 앱을 Google Play에 출시했다고 해서 개발이 끝나는 것은 아닙니다.

앱을 운영하다 보면 버그 수정, 기능 개선, 데이터 업데이트, 보안 패치 등을 위해 새로운 버전을 배포하게 됩니다.

그런데 사용자가 앱을 항상 최신 버전으로 유지하는 것은 아닙니다.

Google Play 자동 업데이트가 비활성화되어 있거나, 업데이트가 아직 설치되지 않았거나, 사용자가 직접 업데이트를 미루는 경우도 있습니다.

또한 앱을 사용하는 사람들에게 Google Play 리뷰를 요청하고 싶을 수도 있습니다.

이때 활용할 수 있는 기능이 다음 두 가지입니다.

  • Play In-App Updates: 사용자가 앱을 종료하고 Play 스토어를 직접 찾아가지 않아도 업데이트 흐름을 시작할 수 있도록 지원

  • Play In-App Review: 앱 내부에서 Google Play 리뷰 작성 흐름을 요청

두 기능은 모두 Google Play에서 제공하는 라이브러리를 통해 사용할 수 있습니다.

하지만 사용법을 잘못 이해하면 업데이트 안내가 반복되거나, 리뷰 요청 API를 호출했는데 아무 화면도 나타나지 않는 문제가 발생할 수 있습니다.

이번 글에서는 이러한 문제를 예방하면서 실제 앱에 적용하는 방법을 살펴보겠습니다.

작성 기준: 2026년 10월. Kotlin, XML, AndroidX Activity Result API, minSdk 24 이상을 기준으로 설명합니다. Play Core 라이브러리 버전과 Google Play 정책은 변경될 수 있으므로 배포 전 공식 문서를 확인해야 합니다.


1. 인앱 업데이트란?

인앱 업데이트는 사용자가 앱을 사용하는 도중 새로운 버전으로 업데이트하도록 안내하는 기능입니다.

일반적인 업데이트 흐름은 다음과 같습니다.

사용자 앱 실행
      ↓
Google Play에서 새 버전 확인
      ↓
Play 스토어로 이동
      ↓
업데이트 버튼 선택
      ↓
업데이트 설치
      ↓
앱 재실행

인앱 업데이트를 사용하면 앱에서 업데이트 절차를 시작할 수 있습니다.

사용자 앱 실행
      ↓
업데이트 가능 여부 확인
      ↓
업데이트 유형 결정
      ↓
Google Play 업데이트 UI
      ↓
다운로드 및 설치

중요한 점은 앱이 APK 파일을 직접 다운로드하고 설치하는 방식이 아니라 Google Play가 제공하는 업데이트 절차를 이용한다는 것입니다.

따라서 일반적인 Play 배포 앱에서 직접 APK 설치 로직을 구현할 필요가 없습니다.


2. Play In-App Updates의 두 가지 방식

Google Play는 크게 두 가지 업데이트 방식을 제공합니다.

구분Flexible UpdateImmediate Update
한국어 표현유연한 업데이트즉시 업데이트
다운로드 중 앱 사용가능일반적으로 업데이트 흐름이 화면을 차지
사용자 경험비교적 자연스러움업데이트를 강하게 유도
설치 시점다운로드 완료 후 설치 요청Play 업데이트 흐름에서 처리
추천 상황일반적인 기능 개선중요한 수정이 필요한 상황

두 방식 중 어느 것이 적합한지는 업데이트의 중요도에 따라 결정해야 합니다.

Flexible Update

앱을 사용하면서 업데이트 파일을 다운로드할 수 있습니다.

다운로드가 완료되면 앱에서 설치를 진행하도록 안내할 수 있습니다.

Immediate Update

업데이트 과정이 앱 화면을 대신하는 형태로 진행됩니다.

보안 문제나 심각한 오류 수정처럼 빠른 업데이트가 중요한 경우 검토할 수 있습니다.

다만 즉시 업데이트도 사용자의 취소나 실패 가능성을 고려해야 합니다.


3. 어떤 업데이트 방식을 선택해야 할까?

모든 업데이트에 Immediate Update를 적용하는 것은 권장하지 않습니다.

예를 들어 다음과 같이 구분할 수 있습니다.

업데이트 내용추천 방식
UI 디자인 개선Flexible
새로운 검색 기능 추가Flexible
이미지 로딩 성능 개선Flexible
일반적인 버그 수정Flexible
데이터 손상 가능성 수정Immediate 검토
심각한 보안 취약점 수정Immediate 검토
서버 API 호환성 문제상황에 따라 Immediate 검토

즉시 업데이트를 사용하더라도 업데이트가 반드시 완료된다고 보장할 수는 없습니다.

따라서 서버 API 호환성 문제처럼 이전 버전이 더 이상 정상 작동할 수 없는 상황에서는 서버 측 호환성 정책과 최소 지원 버전 관리도 함께 설계해야 합니다.


Play In-App Updates 구현

4. Gradle 의존성 추가

앱 모듈의 build.gradle.kts에 Play In-App Updates 라이브러리를 추가합니다.

dependencies {

    implementation(
        "com.google.android.play:app-update:<version>"
    )

    implementation(
        "com.google.android.play:app-update-ktx:<version>"
    )
}

<version>에는 공식 문서에서 확인한 안정 버전을 입력합니다.

라이브러리 버전을 예제에서 임의로 고정하지 않은 이유는 Google Play SDK의 지원 버전과 Android targetSdk 요구사항이 변경될 수 있기 때문입니다.

또한 최신 Play 라이브러리와 프로젝트의 compileSdk, Kotlin 버전이 호환되는지 확인해야 합니다.


5. AppUpdateManager 생성

업데이트 상태를 확인하려면 AppUpdateManager를 사용합니다.

val appUpdateManager =
    AppUpdateManagerFactory.create(
        applicationContext
    )

이 객체를 이용해 업데이트 가능 여부를 조회할 수 있습니다.

appUpdateManager.appUpdateInfo
    .addOnSuccessListener { appUpdateInfo ->

        // 업데이트 상태 확인
    }
    .addOnFailureListener { exception ->

        // 조회 실패 처리
    }

이때 조회 실패를 앱 실행 실패로 연결해서는 안 됩니다.

Google Play 서비스 상태나 네트워크 환경 등에 따라 업데이트 정보를 가져오지 못할 수 있습니다.


6. 업데이트 가능 여부 확인

업데이트 상태는 UpdateAvailability로 확인합니다.

appUpdateManager.appUpdateInfo
    .addOnSuccessListener { info ->

        if (
            info.updateAvailability() ==
            UpdateAvailability.UPDATE_AVAILABLE
        ) {

            // 업데이트 가능
        }
    }

하지만 업데이트가 가능하다는 사실만으로 원하는 업데이트 방식을 사용할 수 있는 것은 아닙니다.

다음 API도 확인해야 합니다.

info.isUpdateTypeAllowed(
    AppUpdateType.FLEXIBLE
)

즉시 업데이트는 다음과 같이 확인합니다.

info.isUpdateTypeAllowed(
    AppUpdateType.IMMEDIATE
)

Google Play에서 허용하는 업데이트 방식만 실행해야 합니다.


7. Activity Result API로 업데이트 실행하기

최신 구현에서는 Activity Result API를 활용할 수 있습니다.

먼저 업데이트 결과를 받을 런처를 등록합니다.

private val updateLauncher =
    registerForActivityResult(
        ActivityResultContracts.StartIntentSenderForResult()
    ) { result ->

        when (result.resultCode) {

            Activity.RESULT_OK -> {
                // 업데이트 흐름의 결과 처리
            }

            Activity.RESULT_CANCELED -> {
                // 사용자가 취소했을 수 있음
            }

            else -> {
                // 실패 또는 기타 결과
            }
        }
    }

다만 이 런처는 IntentSenderRequest를 직접 실행하는 계약입니다.

Play In-App Updates에서 제공하는 업데이트 시작 API와 연결할 때는 라이브러리에서 지원하는 오버로드를 확인해야 합니다.

현재 Play In-App Updates API는 ActivityResultLauncher<IntentSenderRequest>를 받는 업데이트 시작 방식을 제공합니다.


8. Flexible Update 시작하기

유연한 업데이트를 시작하는 기본 구조는 다음과 같습니다.

private fun startFlexibleUpdate(
    info: AppUpdateInfo
) {

    val options =
        AppUpdateOptions.newBuilder(
            AppUpdateType.FLEXIBLE
        ).build()

    try {

        appUpdateManager.startUpdateFlowForResult(
            info,
            updateLauncher,
            options
        )

    } catch (exception: IntentSender.SendIntentException) {

        // 업데이트 시작 실패 처리
    }
}

위 코드는 appUpdateManager와 updateLauncher가 Activity에서 초기화되어 있다고 가정합니다.

사용하는 Play 라이브러리 버전에 따라 예외 및 메서드 시그니처를 확인해야 합니다.

업데이트 시작에 실패하더라도 앱 자체는 계속 사용할 수 있도록 처리하는 것이 좋습니다.


9. Immediate Update 시작하기

즉시 업데이트도 비슷한 방식으로 실행할 수 있습니다.

private fun startImmediateUpdate(
    info: AppUpdateInfo
) {

    val options =
        AppUpdateOptions.newBuilder(
            AppUpdateType.IMMEDIATE
        ).build()

    appUpdateManager.startUpdateFlowForResult(
        info,
        updateLauncher,
        options
    )
}

실제 구현에서는 업데이트 유형이 허용되는지 먼저 확인하고, 실패 상황을 처리해야 합니다.

또한 앱의 모든 화면에서 반복적으로 즉시 업데이트를 시작하지 않도록 주의해야 합니다.


10. AppUpdateInfo 객체 재사용 주의

업데이트를 시작할 때 사용하는 AppUpdateInfo는 한 번의 업데이트 흐름을 시작하는 데 사용하도록 설계되어 있습니다.

따라서 사용자가 업데이트를 취소한 뒤 다시 시작하려면 새로운 AppUpdateInfo를 요청하는 것이 안전합니다.

예를 들어 다음과 같은 흐름입니다.

업데이트 정보 요청
       ↓
AppUpdateInfo 수신
       ↓
업데이트 시작
       ↓
사용자 취소
       ↓
업데이트 정보 다시 요청
       ↓
재시도 여부 결정

이전 객체를 계속 재사용하는 구조는 피하는 것이 좋습니다.


Flexible Update 완료 처리

11. Flexible Update의 중요한 특징

Flexible Update는 다운로드가 완료되었다고 해서 자동으로 앱 설치가 완료되는 것은 아닙니다.

일반적으로 다음 흐름을 처리해야 합니다.

업데이트 시작
      ↓
백그라운드 다운로드
      ↓
다운로드 완료
      ↓
사용자에게 설치 안내
      ↓
completeUpdate()
      ↓
앱 업데이트 완료

따라서 다운로드 상태를 관찰하는 코드가 필요합니다.


12. InstallStateUpdatedListener 등록

업데이트 설치 상태를 관찰하려면 Listener를 사용할 수 있습니다.

private val installStateListener =
    InstallStateUpdatedListener { state ->

        when (state.installStatus()) {

            InstallStatus.DOWNLOADING -> {

                // 다운로드 진행 중
            }

            InstallStatus.DOWNLOADED -> {

                showUpdateDownloadedMessage()
            }

            InstallStatus.FAILED -> {

                // 다운로드 실패 처리
            }
        }
    }

Activity에서 Listener를 등록합니다.

appUpdateManager.registerListener(
    installStateListener
)

더 이상 필요하지 않을 때는 등록을 해제해야 합니다.

appUpdateManager.unregisterListener(
    installStateListener
)

Activity 생명주기와 Listener 등록·해제 시점을 함께 관리해야 합니다.


13. 다운로드 진행률 표시

다운로드 중에는 설치 상태에서 진행률을 계산할 수 있습니다.

val downloaded =
    state.bytesDownloaded()

val total =
    state.totalBytesToDownload()

if (total > 0L) {

    val progress =
        (downloaded * 100L / total).toInt()

    binding.progressBar.progress = progress
}

이 코드는 InstallStatus.DOWNLOADING 상태에서 실행하는 것을 가정합니다.

다운로드 크기를 확인할 수 없는 상황도 있으므로 total > 0L 조건을 확인하는 것이 좋습니다.

또한 진행률 표시가 반드시 필요한 것은 아닙니다.

일반적인 앱에서는 다운로드 완료 시점에 설치 안내를 제공하는 방식만으로도 충분할 수 있습니다.


14. 다운로드 완료 후 설치 안내

Flexible Update가 다운로드되면 사용자에게 설치를 안내할 수 있습니다.

예를 들어 Snackbar를 사용합니다.

Snackbar.make(
    binding.root,
    "업데이트 다운로드가 완료되었습니다.",
    Snackbar.LENGTH_INDEFINITE
)
    .setAction("설치") {

        appUpdateManager.completeUpdate()
    }
    .show()

completeUpdate()는 업데이트 설치 절차를 진행하도록 요청합니다.

앱이 전경에 있는 경우에는 업데이트 완료 과정에서 앱이 재시작될 수 있습니다.

따라서 사용자에게 작성 중인 데이터가 있다면 저장하도록 안내하는 것이 좋습니다.


15. 앱 재실행 후 다운로드 완료 상태 확인

사용자가 업데이트 다운로드를 완료했지만 설치하지 않은 상태에서 앱을 종료할 수 있습니다.

따라서 앱을 다시 실행했을 때 다운로드 완료 상태를 확인해야 합니다.

private fun checkDownloadedUpdate() {

    appUpdateManager.appUpdateInfo
        .addOnSuccessListener { info ->

            if (
                info.installStatus() ==
                InstallStatus.DOWNLOADED
            ) {

                showUpdateDownloadedMessage()
            }
        }
}

이렇게 하면 사용자가 설치를 미룬 업데이트를 다시 안내할 수 있습니다.

다만 안내를 지나치게 자주 표시하지 않도록 사용자 경험을 고려해야 합니다.


Immediate Update 처리

16. 진행 중인 즉시 업데이트 재개

Immediate Update를 진행하던 중 앱이 종료되거나 업데이트 흐름이 중단될 수 있습니다.

이 경우 다음 상태를 확인할 수 있습니다.

appUpdateManager.appUpdateInfo
    .addOnSuccessListener { info ->

        if (
            info.updateAvailability() ==
            UpdateAvailability
                .DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
        ) {

            // 진행 중인 즉시 업데이트 재개 검토
        }
    }

이 상태에서는 새로운 업데이트 정보를 이용해 즉시 업데이트 흐름을 다시 시작할 수 있습니다.

다만 사용자가 취소한 모든 상황에서 무조건 재실행하는 방식은 피해야 합니다.


17. Immediate Update를 강제로 적용할 수 있을까?

인앱 업데이트 API를 사용한다고 해서 앱 개발자가 Google Play 업데이트를 완전히 강제할 수 있는 것은 아닙니다.

사용자는 업데이트를 취소할 수 있으며, 네트워크나 기기 상태 때문에 설치가 실패할 수도 있습니다.

따라서 반드시 최신 버전이 필요한 서비스라면 서버와 앱 사이에 별도의 버전 호환성 정책을 설계해야 합니다.

예를 들어 서버에서 최소 지원 버전을 제공할 수 있습니다.

{
  "minimumSupportedVersionCode": 120,
  "recommendedVersionCode": 125
}

앱은 현재 버전과 비교하여 안내 화면을 표시할 수 있습니다.

하지만 서버 응답만으로 APK 설치가 이루어지는 것은 아닙니다.

실제 업데이트는 Google Play를 통해 진행해야 합니다.


앱 버전 관리

18. versionCode와 versionName의 차이

Android 앱에는 대표적으로 두 가지 버전 정보가 있습니다.

defaultConfig {

    versionCode = 125

    versionName = "1.2.5"
}

각 의미는 다음과 같습니다.

항목설명
versionCodeAndroid 및 Google Play가 버전 순서를 판단하는 정수
versionName사용자에게 표시되는 버전 문자열

일반적으로 새로운 버전을 Google Play에 배포할 때는 versionCode를 증가시켜야 합니다.

반면 versionName은 사용자에게 보여주는 표현이므로 프로젝트의 버전 정책에 따라 관리할 수 있습니다.


19. 추천 버전 관리 방식

개인 개발자가 여러 앱을 운영한다면 버전 정책을 통일하는 것이 편리합니다.

예를 들어 다음과 같이 사용할 수 있습니다.

1.0.0  최초 출시
1.0.1  버그 수정
1.1.0  기능 추가
1.2.0  기능 개선
2.0.0  큰 구조 변경

이 방식은 일반적인 Semantic Versioning 개념을 참고한 것입니다.

하지만 Android의 versionCode는 별도로 증가시켜야 합니다.

예를 들어 다음과 같습니다.

versionCode = 126
versionName = "1.2.6"

Google Play는 버전 문자열이 아니라 버전 코드와 배포 조건을 기준으로 업데이트 가능 여부를 판단합니다.


20. Product Flavor별 버전 관리

여러 Product Flavor를 사용하는 프로젝트에서는 버전 관리에 주의해야 합니다.

예를 들어 다음과 같은 구조입니다.

flavorDimensions += "animal"

productFlavors {

    create("bird") {
        dimension = "animal"
        applicationId = "com.example.koreanbirds"
    }

    create("fish") {
        dimension = "animal"
        applicationId = "com.example.koreanfish"
    }
}

각 Flavor가 서로 다른 applicationId를 사용한다면 Google Play에서는 별도 앱으로 관리됩니다.

따라서 버전 코드와 배포 일정도 앱별로 관리할 수 있습니다.

반대로 동일한 applicationId의 빌드 변형을 배포할 때는 버전 코드 충돌이 발생하지 않도록 주의해야 합니다.


업데이트 우선순위 관리

21. Google Play 업데이트 우선순위

Google Play Developer API를 이용한 배포 과정에서는 업데이트 우선순위를 지정할 수 있습니다.

인앱 업데이트 API에서는 다음 값을 확인할 수 있습니다.

info.updatePriority()

업데이트 우선순위는 일반적으로 0부터 5까지의 값으로 표현됩니다.

값의미
0기본 우선순위
1~2상대적으로 낮은 우선순위
3~4높은 우선순위로 활용 가능
5가장 높은 우선순위

다만 우선순위 값만으로 업데이트 방식이 자동 결정되는 것은 아닙니다.

앱에서 정책을 직접 구현해야 합니다.


22. 우선순위에 따라 업데이트 방식 선택

예를 들어 다음과 같이 구성할 수 있습니다.

private fun chooseUpdateType(
    info: AppUpdateInfo
): Int? {

    val priority = info.updatePriority()

    return when {

        priority >= 4 &&
        info.isUpdateTypeAllowed(
            AppUpdateType.IMMEDIATE
        ) -> AppUpdateType.IMMEDIATE

        info.isUpdateTypeAllowed(
            AppUpdateType.FLEXIBLE
        ) -> AppUpdateType.FLEXIBLE

        else -> null
    }
}

이 코드는 예시 정책입니다.

실제 앱에서는 업데이트 내용, 사용자 작업 상태, 네트워크 환경 등을 함께 고려해야 합니다.

특히 Immediate Update를 실행하기 전에 사용자가 작성 중인 메모나 데이터가 있는지 확인하는 것이 좋습니다.


23. 업데이트가 가능한 지 며칠 지났는지 확인

Play In-App Updates에서는 업데이트가 가능해진 뒤 경과한 일수를 확인할 수 있습니다.

val days =
    info.clientVersionStalenessDays()

이 값은 nullable일 수 있습니다.

따라서 다음과 같이 처리해야 합니다.

if (days != null && days >= 7) {

    // 업데이트 안내 강화 검토
}

이 기능을 이용하면 업데이트가 출시된 직후에는 일반 안내를 표시하고, 오랫동안 업데이트하지 않은 사용자에게는 더 적극적으로 안내하는 정책을 만들 수 있습니다.

다만 업데이트 경과 일수와 우선순위는 별개의 정보입니다.


Play In-App Review 구현

24. In-App Review란?

In-App Review는 사용자가 앱을 사용하던 중 Google Play 리뷰 작성 흐름을 요청하는 기능입니다.

일반적으로 사용자가 Play 스토어를 직접 찾아가지 않고도 리뷰를 작성할 수 있도록 설계되었습니다.

하지만 중요한 제한이 있습니다.

In-App Review API를 호출했다고 해서 리뷰 작성 화면이 반드시 표시되는 것은 아닙니다.

Google Play는 내부 정책에 따라 리뷰 UI 표시 여부를 결정합니다.

따라서 API 호출 결과만으로 사용자가 리뷰를 작성했다고 판단해서는 안 됩니다.


25. Gradle 의존성 추가

앱 모듈에 Play Review 라이브러리를 추가합니다.

dependencies {

    implementation(
        "com.google.android.play:review:<version>"
    )

    implementation(
        "com.google.android.play:review-ktx:<version>"
    )
}

<version>은 공식 문서에서 확인한 버전을 사용합니다.

In-App Updates와 In-App Review는 별도의 라이브러리입니다.

따라서 두 기능을 모두 사용한다면 각각 필요한 의존성을 추가해야 합니다.


26. ReviewManager 생성

리뷰 요청을 위해 ReviewManager를 생성합니다.

val reviewManager =
    ReviewManagerFactory.create(
        applicationContext
    )

그리고 리뷰 요청 정보를 가져옵니다.

reviewManager.requestReviewFlow()
    .addOnCompleteListener { task ->

        if (task.isSuccessful) {

            val reviewInfo = task.result

            // 리뷰 흐름 실행
        }
    }

리뷰 요청 정보 조회에 실패하더라도 앱 기능은 정상적으로 계속되어야 합니다.


27. 리뷰 작성 흐름 실행

리뷰 요청 정보를 받았다면 다음과 같이 실행할 수 있습니다.

private fun requestInAppReview() {

    val manager =
        ReviewManagerFactory.create(this)

    manager.requestReviewFlow()
        .addOnCompleteListener { requestTask ->

            if (requestTask.isSuccessful) {

                val reviewInfo =
                    requestTask.result

                manager.launchReviewFlow(
                    this,
                    reviewInfo
                ).addOnCompleteListener {

                    // 리뷰 UI 표시 여부나
                    // 리뷰 작성 여부를 알 수 없음
                }
            }
        }
}

이 코드는 Activity 내부에서 실행하는 예제입니다.

가장 중요한 점은 launchReviewFlow()의 완료 콜백입니다.

이 콜백은 사용자가 리뷰를 작성했다는 의미가 아닙니다.

또한 리뷰 UI가 실제로 표시되었는지도 확인할 수 없습니다.

따라서 리뷰 작성 완료 메시지를 표시하거나 리뷰 작성 여부를 앱 내부에 저장하는 방식은 피해야 합니다.


28. 리뷰 요청은 언제 하는 것이 좋을까?

리뷰 요청은 사용자가 앱에서 긍정적인 경험을 한 뒤 자연스럽게 표시하는 것이 좋습니다.

예를 들어 다음과 같은 시점을 고려할 수 있습니다.

  • 앱을 여러 차례 사용한 이후

  • 주요 기능을 정상적으로 완료한 이후

  • 사용자가 원하는 정보를 성공적으로 찾은 이후

  • 앱에서 의미 있는 작업을 완료한 이후

반대로 다음 상황에서는 리뷰 요청을 피하는 것이 좋습니다.

  • 앱 최초 실행 직후

  • 로그인이나 권한 요청 직후

  • 오류가 발생한 직후

  • 사용자가 중요한 작업을 진행 중일 때

  • 업데이트 설치 직후 사용 경험이 충분하지 않을 때

리뷰 요청의 핵심은 사용자의 작업을 방해하지 않는 것입니다.


29. 리뷰 요청 횟수를 앱에서 제한하기

Google Play 자체적으로 리뷰 UI 표시 빈도를 제한할 수 있습니다.

하지만 앱에서도 요청 시점을 관리하는 것이 좋습니다.

예를 들어 DataStore에 다음과 같은 정보를 저장할 수 있습니다.

review_prompt_last_requested_at

그리고 일정 기간 동안 반복적으로 리뷰 요청 API를 호출하지 않도록 할 수 있습니다.

다만 이 값은 리뷰를 작성한 날짜가 아니라 앱이 리뷰 흐름을 요청한 날짜입니다.

Google Play API는 실제 리뷰 작성 여부를 앱에 알려주지 않기 때문입니다.


30. DataStore를 이용한 리뷰 요청 정책

예를 들어 다음과 같은 정책을 생각할 수 있습니다.

앱 실행 횟수 5회 이상
        +
주요 기능 사용 경험 있음
        +
최근 30일 이내 리뷰 요청 없음
        ↓
리뷰 요청 검토

이 정책은 예시이며 Google Play의 공식 표시 기준은 아닙니다.

실제 구현에서는 다음과 같은 데이터가 필요할 수 있습니다.

data class ReviewPromptState(

    val launchCount: Int = 0,

    val lastRequestedAt: Long = 0L
)

하지만 단순히 앱 실행 횟수만으로 리뷰 요청을 결정하는 것은 적절하지 않을 수 있습니다.

사용자가 앱에서 실제로 가치 있는 경험을 했는지도 함께 고려하는 것이 좋습니다.


31. 리뷰 요청 전에 별점 선택 Dialog를 보여줘도 될까?

일부 앱에서는 리뷰 요청 전에 다음과 같은 화면을 표시합니다.

앱이 마음에 드시나요?

★★★★★

[리뷰 작성]

하지만 긍정적인 평가를 할 사용자만 Google Play 리뷰로 보내고, 부정적인 평가를 할 사용자는 다른 화면으로 보내는 방식은 리뷰 유도 정책 측면에서 문제가 될 수 있습니다.

따라서 사용자를 평가 성향에 따라 차별적으로 분기시키는 방식은 피하는 것이 좋습니다.

리뷰 요청은 중립적으로 제공하고, 앱 내 피드백 기능은 별도로 구성하는 것이 바람직합니다.


32. 리뷰 요청이 표시되지 않는 이유

In-App Review API를 호출했는데 아무 화면도 나타나지 않을 수 있습니다.

대표적인 이유는 다음과 같습니다.

  • Google Play의 내부 표시 빈도 제한

  • 테스트 환경의 제한

  • 앱 설치 출처

  • Google Play 계정 및 앱 상태

  • 기기 환경

  • API 호출 시점

중요한 점은 리뷰 UI가 표시되지 않는 것이 반드시 오류는 아니라는 것입니다.

따라서 리뷰 화면이 나타나지 않았다고 해서 무한 재시도하는 로직을 구현해서는 안 됩니다.


33. In-App Review 테스트 방법

Google Play에서는 In-App Review 테스트를 위한 방법을 제공합니다.

대표적으로 다음 환경을 사용할 수 있습니다.

  • Google Play 내부 테스트 트랙

  • 내부 앱 공유

  • FakeReviewManager를 이용한 테스트

다만 FakeReviewManager는 실제 Google Play 리뷰 UI를 표시하는 도구가 아닙니다.

앱의 리뷰 요청 코드 흐름을 테스트하기 위한 용도입니다.

따라서 실제 사용자에게 리뷰 UI가 어떻게 표시되는지 확인하려면 Google Play 테스트 환경에서 별도로 검증해야 합니다.


Google Play 테스트

34. In-App Updates 테스트 조건

Play In-App Updates는 일반적인 로컬 Debug APK 설치만으로 실제 업데이트 흐름을 완전히 테스트하기 어렵습니다.

테스트에는 Google Play가 관리하는 설치 및 업데이트 환경이 필요합니다.

대표적으로 다음과 같은 방식이 있습니다.

  1. 낮은 versionCode의 앱을 테스트 트랙에 배포

  2. 테스트 계정으로 앱 설치

  3. 더 높은 versionCode의 앱을 배포

  4. 업데이트가 제공되는 상태 확인

  5. 앱에서 In-App Updates 실행

실제 업데이트가 제공되기까지는 Google Play의 배포 및 반영 시간이 필요할 수 있습니다.

또한 테스트 계정, 앱 설치 출처, 서명 상태 등이 올바르게 구성되어야 합니다.


35. 내부 앱 공유를 이용한 테스트

Google Play 내부 앱 공유 기능을 이용하면 테스트용 빌드를 배포할 수 있습니다.

하지만 내부 앱 공유를 이용한 테스트는 일반적인 프로덕션 배포와 동작 조건이 다를 수 있습니다.

따라서 최종 배포 전에 내부 테스트 트랙 등 실제 Play 배포 흐름에 가까운 환경에서도 검증하는 것이 좋습니다.

특히 다음 항목을 확인해야 합니다.

  • applicationId 일치

  • 서명 호환성

  • versionCode 증가

  • Google Play 설치 출처

  • 업데이트 제공 여부

  • 업데이트 유형 허용 여부


운영 중 발생하는 문제

36. 업데이트가 감지되지 않는 경우

다음 사항을 확인합니다.

증상확인할 내용
UPDATE_NOT_AVAILABLE실제로 더 높은 버전이 제공되는지
업데이트 조회 실패Play 환경과 네트워크 상태
Debug 앱에서 동작하지 않음설치 출처와 서명
테스트 계정에서만 실패테스트 트랙 참여 상태
일부 기기에서만 실패기기 호환성과 배포 대상
업데이트 UI가 열리지 않음isUpdateTypeAllowed 결과
업데이트 후 이전 화면이 남음Activity 재생성 및 프로세스 상태

업데이트 기능을 테스트할 때는 Google Play Console의 배포 상태를 함께 확인해야 합니다.


37. 업데이트 중 사용자 데이터 보호

앱 업데이트 과정에서는 Activity가 종료되거나 프로세스가 다시 시작될 수 있습니다.

따라서 사용자 데이터는 적절한 시점에 저장해야 합니다.

예를 들어 조류 도감 앱에서 메모를 작성하고 있다면 다음 사항을 고려해야 합니다.

  • 메모 임시 저장

  • SQLite 트랜잭션 처리

  • ViewModel 상태 관리

  • SavedStateHandle 활용

  • 업데이트 완료 후 데이터 유지 확인

특히 Flexible Update에서 설치를 시작하기 전에 작성 중인 데이터를 저장하도록 안내하는 것이 좋습니다.

업데이트 자체가 사용자 데이터를 삭제하는 것은 일반적인 동작이 아니지만, 앱의 데이터 마이그레이션 코드에 문제가 있다면 업데이트 이후 데이터 손상이 발생할 수 있습니다.


38. SQLite 데이터베이스 버전과 앱 업데이트

앱을 업데이트하면서 SQLite 스키마를 변경하는 경우에는 데이터베이스 마이그레이션을 함께 고려해야 합니다.

예를 들어 다음과 같은 변경입니다.

ALTER TABLE bird_memos
ADD COLUMN updated_at INTEGER;

기존 사용자의 메모 데이터를 유지하면서 새로운 컬럼을 추가해야 합니다.

이때 앱 업데이트와 SQLite 데이터베이스 업데이트는 서로 다른 개념입니다.

Google Play 앱 업데이트
        ↓
새로운 APK 설치
        ↓
앱 실행
        ↓
SQLiteOpenHelper 버전 확인
        ↓
필요한 DB 마이그레이션
        ↓
기존 사용자 데이터 유지

따라서 인앱 업데이트 기능을 구현했다고 해서 SQLite 데이터 마이그레이션 문제가 자동으로 해결되는 것은 아닙니다.


39. 업데이트 기능을 어디에 배치할까?

일반적인 앱에서는 다음 위치를 고려할 수 있습니다.

  • MainActivity의 초기 진입 시점

  • 설정 화면의 '업데이트 확인' 메뉴

  • 중요한 업데이트가 있을 때 안내 화면

  • 앱 재진입 시 다운로드 완료 상태 확인

하지만 앱 실행 시마다 여러 화면에서 동시에 업데이트 정보를 요청하는 것은 피해야 합니다.

추천 구조는 다음과 같습니다.

MainActivity
      │
      ▼
UpdateCoordinator
      │
      ▼
AppUpdateManager
      │
      ▼
Google Play

업데이트 흐름은 Activity와 연결되는 부분이 많으므로 일반적인 데이터 Repository와 동일하게 설계하기보다 UI 생명주기를 고려한 별도 Coordinator로 관리하는 것이 적합할 수 있습니다.


40. In-App Updates와 In-App Review를 함께 사용할 때

업데이트 안내와 리뷰 요청을 동시에 표시해서는 안 됩니다.

예를 들어 다음과 같은 흐름은 좋지 않습니다.

앱 실행
   ↓
업데이트 요청
   ↓
리뷰 요청
   ↓
알림 권한 요청

사용자에게 여러 종류의 시스템 UI가 연속적으로 표시되면 앱 사용 경험이 나빠질 수 있습니다.

따라서 다음과 같은 우선순위를 고려할 수 있습니다.

앱 실행
   ↓
중요한 업데이트 여부 확인
   ↓
필요하면 업데이트 안내
   ↓
사용자 주요 기능 이용
   ↓
적절한 시점에 리뷰 요청 검토

리뷰 요청은 업데이트 안내보다 우선순위를 낮게 두는 것이 일반적으로 자연스럽습니다.


41. 실제 앱 운영 체크리스트

인앱 업데이트

  • Play In-App Updates 라이브러리 추가

  • AppUpdateManager 생성

  • 업데이트 가능 여부 확인

  • 업데이트 유형 허용 여부 확인

  • Flexible Update 구현

  • Immediate Update 필요성 검토

  • InstallStateUpdatedListener 등록

  • Listener 해제 처리

  • 다운로드 진행 상태 처리

  • 다운로드 완료 후 설치 안내

  • 앱 재실행 시 다운로드 완료 상태 확인

  • 진행 중인 Immediate Update 재개 처리

  • 업데이트 취소 및 실패 처리

  • versionCode 증가 확인

  • Google Play 테스트 트랙 검증

인앱 리뷰

  • Play Review 라이브러리 추가

  • ReviewManager 생성

  • requestReviewFlow() 구현

  • launchReviewFlow() 구현

  • 리뷰 UI 표시 여부를 가정하지 않음

  • 리뷰 작성 완료 여부를 추정하지 않음

  • 리뷰 요청 시점 정책 수립

  • 과도한 반복 요청 방지

  • 별점에 따른 차별적 리뷰 유도 방지

  • FakeReviewManager로 코드 흐름 테스트

  • Google Play 테스트 환경 검증


42. 마무리

Android 앱을 출시한 이후에는 업데이트와 사용자 피드백 관리가 중요합니다.

Play In-App Updates를 이용하면 사용자가 앱 내부에서 업데이트 절차를 시작할 수 있습니다.

또한 Play In-App Review를 이용하면 앱 사용 중 Google Play 리뷰 작성 흐름을 요청할 수 있습니다.

하지만 두 기능 모두 Google Play가 제공하는 시스템 흐름을 사용하므로 앱 개발자가 모든 동작을 직접 제어할 수 있는 것은 아닙니다.

특히 다음 원칙을 기억하는 것이 좋습니다.

  1. 일반적인 업데이트에는 Flexible Update를 우선 고려합니다.

  2. 중요한 수정이 필요한 경우 Immediate Update를 검토합니다.

  3. 업데이트 가능 여부와 업데이트 유형 허용 여부를 모두 확인합니다.

  4. Flexible Update에서는 다운로드 완료 후 설치 절차를 처리합니다.

  5. 업데이트 취소와 실패 상황에서도 앱이 안정적으로 동작해야 합니다.

  6. versionCode를 올바르게 관리합니다.

  7. 리뷰 요청 API 호출이 실제 리뷰 화면 표시를 보장하지는 않습니다.

  8. 사용자가 리뷰를 작성했는지 앱에서 알 수 없습니다.

  9. 리뷰 요청은 사용자가 의미 있는 경험을 한 뒤 자연스럽게 진행합니다.

  10. 실제 Google Play 테스트 환경에서 최종 검증합니다.

여러 앱을 운영하는 개인 개발자라면 업데이트와 리뷰 요청 기능을 공통 구조로 정리해 두는 것도 좋은 방법입니다.

다만 업데이트 정책과 리뷰 요청 시점은 앱의 특성에 맞게 조정해야 합니다.

인앱 업데이트와 인앱 리뷰의 핵심은 사용자를 강제로 움직이게 만드는 것이 아니라, 필요한 순간에 자연스럽게 다음 행동을 할 수 있도록 돕는 것입니다.


참고 자료

태그: Android, Kotlin, Google Play, In-App Updates, In-App Review, Flexible Update, Immediate Update, AppUpdateManager, ReviewManager, versionCode, versionName, Google Play Console, Android 앱 업데이트, Android 앱 리뷰, Android 앱 운영