In-App Updates

In-App Updates and In-App Review

OTA After Launch and Live Updates and CI/CD pushed JavaScript over the air; a native app changes only when Play installs a new APK. Two Play libraries bring the store into the app: app-update-ktx 2.1.0 lets the app offer its own update, and review-ktx 2.0.2 shows Play's rating card without leaving it. Both talk to the Play Store app, and both answer with Play services Task objects that await() from kotlinx-coroutines-play-services suspends on.

An update is flexible (downloads while the reader keeps browsing) or immediate (a full-screen flow that blocks the app until it has updated). BookNest writes the choice as a pure function:

play/PlayUpdates.kt: BookNest's update ruleKotlin
enum class UpdateMode { NONE, FLEXIBLE, IMMEDIATE }
// 5.38.6 BookNest's rule: urgent or two-week-old updates block, the rest download quietly
fun updateModeFor(info: AppUpdateInfo): UpdateMode {
  if (info.updateAvailability() != UpdateAvailability.UPDATE_AVAILABLE) return UpdateMode.NONE
  val urgent = info.updatePriority() >= 4 || (info.clientVersionStalenessDays() ?: 0) >= 14
  return when {
    urgent && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> UpdateMode.IMMEDIATE
    info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> UpdateMode.FLEXIBLE
    else -> UpdateMode.NONE
  }
}

checkAndStartUpdate() in the same file applies the rule and calls startUpdateFlowForResult() with a StartIntentSenderForResult launcher, plus a listener that calls completeUpdate() when a flexible download reaches DOWNLOADED. Play's fake managers let a Robolectric test (Robolectric) pin the rule; modeFor builds a FakeAppUpdateManager(context), applies the setup, and returns updateModeFor(fake.appUpdateInfo.await()):

PlayUpdatesTest.kt: one of five cases (excerpt)Kotlin
@Test fun urgentUpdateIsImmediate() = runTest {
  assertEquals(UpdateMode.IMMEDIATE, modeFor {
    setUpdateAvailable(10001)
    setUpdatePriority(5)                               // set when the release is uploaded
  })
}
Output
PlayUpdatesTest > reviewFlowCompletesWithoutUi STANDARD_OUT
    review info: ReviewInfo{pendingIntent=PendingIntent{31f5837b: null}, isNoOp=false}
...
PlayUpdatesTest > urgentUpdateIsImmediate PASSED
PlayUpdatesTest > noUpdate PASSED

All five passed, one getting a ReviewInfo from FakeReviewManager. On the emulator the real managers fail: its Google APIs image has a com.android.vending package but not the Play Store services the libraries bind to, as BookNest's --ez playCheck true switch logged (times and PIDs trimmed):

Output of 213
W ActivityManager: Unable to start service Intent {
    act=com.google.android.play.core.install.BIND_UPDATE_SERVICE xflg=0x4
    pkg=com.android.vending } U=0: not found
W BookNestPlay: update check failed: xg0: Failed to bind to the service.
W BookNestPlay: review flow failed: xg0: Failed to bind to the service.

(xg0 is the library's exception class, renamed by R8 234 .) Test the real flow through internal app sharing (Testing Tracks). For reviews, Google's rules are strict: ask once the reader has used the app enough to judge it (after an order arrives, not at launch), never from a "Rate us" button, and never after a question such as "Do you like BookNest?". A quota means launchReviewFlow() may show nothing; carry on regardless.