SuperAPI AI Directory

pbl-v8-to-v9-migration

Coding & development

View repository

Updated 10-16 07:58

About this skill

Phase 0: Intent

Announce to the user: "I will migrate this project from PBL 8 to PBL 9."

State the key fact up front so the user calibrates effort correctly: PBL 9.0.0 (released 2026-05-19) removed no APIs. PBL 8.0.0 was the breaking release that deleted the Sku* family, queryPurchaseHistoryAsync, and the parameterless enablePendingPurchases(). PBL 9 builds on that surface with one target SDK bump, two compatibility adjustments, and one optional new feature. RevenueCat characterizes it as a single sprint of work for a v8 app.

If the project still references SkuDetails, querySkuDetailsAsync, SkuType, or the parameterless enablePendingPurchases(), the real baseline is pre-v8. Stop and route the user to pbl-v7-to-v9-migration, which does the v8 work first.

Phase 1: Discovery

Gather the facts before changing anything.

  1. Locate the dependency. Open build.gradle, build.gradle.kts, or libs.versions.toml and find com.android.billingclient:billing or billing-ktx. Confirm the version reads 8.x.x. If it reads 7.x or lower, switch to the pbl-v7-to-v9-migration skill.
  2. Check androidx.core. Find the androidx.core:core / core-ktx version. v9's error reclassification requires androidx.core 1.9 or later. Record the current value.
  3. Check compile and target SDK. Read compileSdk and targetSdk. v9 targets API 35. Note both values.
  4. Scan for developer provided billing. Grep for DeveloperProvidedBillingDetails, getLinkUri, enableDeveloperBillingOption, and enableBillingProgram. These are the External Payments APIs from PBL 8.3. If any hit, the nullable getLinkUri() change in Phase 3 applies. If none hit, you can skip that step.
  5. Scan error handling. Grep for BillingResponseCode.ERROR and BillingResponseCode.BILLING_UNAVAILABLE. Note every place that branches on a generic ERROR for an unavailable store, because v9 reclassifies the blocked store case.
  6. Note in app messaging usage. Grep for showInAppMessages and InAppMessageParams. If present, the optional price increase messaging step applies.

Report back: "You are on PBL [current]. androidx.core is [version], targetSdk is [value]. I found [N] developer provided billing sites and [M] generic ERROR branches to review. Planning a migration to PBL 9.0.0."

Phase 2: Plan

Build the checklist. Only four items are mandatory; the rest are conditional.

Step Mandatory? What it touches
Bump dependency to 9.0.0 Yes build.gradle / version catalog
Ensure androidx.core 1.9+ Yes (to get the reclassified error) build.gradle / version catalog
Bump compileSdk / targetSdk to 35 Yes build.gradle, manifest
Update blocked store error handling If you branch on generic ERROR for store availability PurchasesUpdatedListener, connection error paths
Handle nullable getLinkUri() Only if you use developer provided billing (PBL 8.3 External Payments) external payment launch code
Adopt in app price increase messaging Optional paywall / app resume code

There is no rename map and no removed API table for v9. If you find yourself writing one, you are looking at the cumulative v8 migration guide, which restates v8.0.0 removals. Do not reintroduce them here.

Present the plan to the user as a checklist and confirm before editing files.

Phase 3: Execute

Step 1: Bump the dependency

// Kotlin
implementation("com.android.billingclient:billing-ktx:9.0.0")
// Java/Groovy
implementation "com.android.billingclient:billing:9.0.0"

Sync Gradle. Because v9 removed nothing, expect a clean compile with no forced edits. The remaining steps are about correctly handling v9 behavior, not fixing compile errors.

Step 2: Ensure androidx.core 1.9 or later

The error code reclassification in Step 4 ships behind androidx.core. Confirm the resolved version is 1.9+.

implementation("androidx.core:core-ktx:1.13.1")

Step 3: Bump compileSdk and targetSdk to 35

android {
    compileSdk = 35
    defaultConfig {
        targetSdk = 35
        minSdk = 23 // unchanged from PBL 8.0
    }
}

minSdk stays at 23 (set in PBL 8.0). Apps on an older compileSdk may see manifest merger warnings until they align to 35.

Step 4: Update blocked Play Store error handling

When the Play Store app is blocked by the system (OEM customized kids mode, parental controls, or managed enterprise devices), v9 reports BILLING_UNAVAILABLE instead of the generic ERROR it returned in v8. The BillingResult carries a "Play Store is blocked" debug message.

when (billingResult.responseCode) {
    BillingResponseCode.BILLING_UNAVAILABLE -> {
        if (billingResult.debugMessage.contains("Play Store is blocked")) {
            showBlockedStoreFallback() // kids tablet / enterprise device messaging
        } else {
            showBillingUnavailable()   // version too old, no Google account, etc.
        }
    }
    BillingResponseCode.ERROR -> showGenericRetry()
    // ...
}

Audit every site that previously treated a generic ERROR as "store unavailable" and move the blocked case onto BILLING_UNAVAILABLE. See error-handling for the full classification.

Step 5: Handle nullable getLinkUri() (developer provided billing only)

PBL 9 marks DeveloperProvidedBillingDetails.getLinkUri() as @Nullable. The external payment link may not be resolved at selection time, so guard against both null and empty string before parsing.

val linkUri = details.linkUri
if (!linkUri.isNullOrEmpty()) {
    startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(linkUri)))
} else {
    showExternalPaymentUnavailableState()
}

Skip this step if Phase 1 found no developer provided billing usage.

Step 6: Adopt in app messaging for opt in price increases (optional)

PBL 9 lets you surface an upcoming opt in price increase inside the app so the user can accept the new price without leaving for the Play Store. This reuses the existing showInAppMessages entry point. Google shows the message starting the first day the user can accept the increase, at most once every 7 days.

val params = InAppMessageParams.newBuilder()
    .addInAppMessageCategoryToShow(
        InAppMessageParams.InAppMessageCategoryId.TRANSACTIONAL
    ).build()

billingClient.showInAppMessages(activity, params) { result ->
    if (result.responseCode ==
        InAppMessageResult.InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED
    ) {
        refreshSubscriptionStatus() // user accepted the new price in the dialog
    }
}

The opt in price increase message is delivered through the same TRANSACTIONAL category already used for payment recovery; v9 adds price increases to what that category can surface. Confirm category behavior against the in app messaging reference for your library version before shipping. See price-changes for the full opt in increase timeline and payment-recovery for the existing showInAppMessages pattern.

Phase 4: Verify

  1. Clean compile. Run ./gradlew clean assembleDebug. v9 should compile without forced changes; investigate any error as a sign the baseline was actually pre-v8.
  2. Unit tests. Run ./gradlew test.
  3. Blocked store path. On a device or emulator where the Play Store is restricted (kids mode, managed profile), confirm the response code is now BILLING_UNAVAILABLE with the "Play Store is blocked" debug message, and that your fallback UI shows.
  4. Nullable link URI. If you use developer provided billing, force a state where the link URI is unavailable and confirm no Uri.parse("") crash.
  5. targetSdk 35. Confirm the build merges manifests cleanly at targetSdk = 35 and the app behaves correctly under Android 15 behavior changes.
  6. Price increase messaging. If adopted, configure an opt in increase in the Play Console and confirm the in app message renders and SUBSCRIPTION_STATUS_UPDATED refreshes entitlement state.
  7. Internal test track rollout. Publish to the internal track, monitor crash reports and billing success rates for at least one billing cycle before promoting.

Report back a final summary: the old version, the new version (9.0.0), the conditional steps that applied, new behavior adopted, and the verification evidence.

References