Phase 0: Intent
Announce to the user: "I will migrate this project from PBL 7 to PBL 9 in two stages."
State the structure clearly. There is no direct v7 to v9 shortcut. Almost all of the breaking work belongs to v8.0.0 (the Sku* removals, the proration to replacement mode rename, alternative billing renames, the pending purchases params, the queryProductDetailsAsync result shape). v9 itself removed nothing and only adds a target SDK bump plus three small adjustments. So this migration is:
- Stage A — v7 to v8: the heavy, mechanical rename work.
- Stage B — v8 to v9: the small delta.
This skill is self contained, but each stage links to the dedicated single hop skill if you want the long form: pbl-v7-to-v8-migration and pbl-v8-to-v9-migration.
Urgency: PBL 7 stops accepting new app and update submissions on August 31, 2026. After that date you cannot ship any release (including security fixes) built on v7, so plan to be in production on v9 with margin for monitoring.
Phase 1: Discovery
- Locate the dependency. Find
com.android.billingclient:billinginbuild.gradle(.kts)orlibs.versions.toml. Confirm it reads7.x(or5.x/6.xcompiling against v7 APIs — the same steps apply). - Scan for every v7 era identifier. Grep the repo for each item in the Stage A rename map below. Record every hit; each is an edit site.
- Check SDK levels. Read
minSdk,compileSdk,targetSdk. You will moveminSdkto 23 andtargetSdkto 35 across the migration. - Check androidx.core. Record the version; Stage B needs 1.9+.
- Note alternative billing and developer provided billing usage. Grep for
enableAlternativeBilling,AlternativeBillingListener,AlternativeChoiceDetails, andDeveloperProvidedBillingDetails.
Report back: "You are on PBL [current]. I found [N] v7 call sites across [M] files. Planning a two stage migration: v7 to v8, then v8 to v9.0.0."
Phase 2: Plan
Stage A — v7 to v8 removed and renamed APIs
Every item here is a breaking change removed in PBL 8.0.0. (Full depth: pbl-v7-to-v8-migration.)
| Area | PBL 7 (removed) | PBL 8 replacement |
|---|---|---|
| Product queries | querySkuDetailsAsync() |
queryProductDetailsAsync() |
| Product details type | SkuDetails |
ProductDetails |
| Query params | SkuDetailsParams |
QueryProductDetailsParams |
| Response listener | SkuDetailsResponseListener |
ProductDetailsResponseListener |
| Product type enum | BillingClient.SkuType |
BillingClient.ProductType |
| Flow params list | setSkuDetailsList() |
setProductDetailsParamsList() |
| Purchase history | queryPurchaseHistoryAsync(), QueryPurchaseHistoryParams |
queryPurchasesAsync(QueryPurchasesParams, ...) (active/pending only) |
| Purchases query | queryPurchasesAsync(String, listener) |
queryPurchasesAsync(QueryPurchasesParams, listener) |
| Pending purchases | enablePendingPurchases() (no args) |
enablePendingPurchases(PendingPurchasesParams) |
| Alternative billing toggle | enableAlternativeBilling() |
enableUserChoiceBilling() |
| Alternative billing listener | AlternativeBillingListener |
UserChoiceBillingListener |
| Alternative choice details | AlternativeChoiceDetails |
UserChoiceDetails |
| Proration enum | ProrationMode |
ReplacementMode |
| Proration setter | setReplaceProrationMode() |
setSubscriptionReplacementMode() |
ProrationMode to ReplacementMode constant map
ProrationMode (PBL 7) |
ReplacementMode (PBL 8) |
|---|---|
IMMEDIATE_WITH_TIME_PRORATION |
WITH_TIME_PRORATION |
IMMEDIATE_AND_CHARGE_PRORATED_PRICE |
CHARGE_PRORATED_PRICE |
IMMEDIATE_AND_CHARGE_FULL_PRICE |
CHARGE_FULL_PRICE |
IMMEDIATE_WITHOUT_PRORATION |
WITHOUT_PRORATION |
DEFERRED |
DEFERRED |
| (none) | KEEP_EXISTING (PBL 8.1+) |
Stage B — v8 to v9 delta
No removals. Four items, most conditional. (Full depth: pbl-v8-to-v9-migration.)
- Bump dependency to 9.0.0 and
targetSdkto 35. - Ensure androidx.core 1.9+.
- Blocked Play Store now returns
BILLING_UNAVAILABLEinstead of genericERROR. DeveloperProvidedBillingDetails.getLinkUri()is now@Nullable(only if you use developer provided billing).- Optional: in app messaging for opt in price increases.
Present both stages as one checklist and confirm before editing.
Phase 3: Execute
Do Stage A end to end, get a clean compile, then do Stage B. Do not interleave: a clean v8 build is the checkpoint that proves the heavy work is done.
Stage A: migrate to PBL 8
Bump to the latest v8 (target 8.1+ so you also pick up
SubscriptionProductReplacementParamsandincludeSuspendedSubscriptions).implementation("com.android.billingclient:billing-ktx:8.1.0")Fix the
BillingClientbuilder — replace parameterlessenablePendingPurchases()and add auto reconnection.BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .enablePendingPurchases( PendingPurchasesParams.newBuilder() .enableOneTimeProducts() .enablePrepaidPlans() .build() ) .enableAutoServiceReconnection() .build()Replace
querySkuDetailsAsyncwithqueryProductDetailsAsyncand switch display code offSkuDetailsontoProductDetails(base plans, offers, pricing phases). Read theQueryProductDetailsResult.unfetchedProductListin the same pass.Replace
queryPurchaseHistoryAsyncwithqueryPurchasesAsync(active/pending only). Move historical lookups to the backend via the Play Developer API.Rename alternative billing to user choice billing at every grep hit from Phase 1.
Replace
ProrationModewithReplacementModeusing the constant map, and renamesetReplaceProrationModetosetSubscriptionReplacementMode. On 8.1+, preferSubscriptionProductReplacementParamsattached to the product params.Bump
minSdkto 23. Review ProGuard/R8 keep rules that referenced removed classes (SkuDetails,AlternativeBillingListener).Compile clean and run tests. This is the checkpoint. Resolve every reference to a removed v7 API before moving on.
Stage B: migrate to PBL 9
Only after a clean v8 build, do the small v9 delta (full depth: pbl-v8-to-v9-migration):
Bump the dependency to 9.0.0.
implementation("com.android.billingclient:billing-ktx:9.0.0")Ensure androidx.core 1.9+ and bump
compileSdk/targetSdkto 35 (minSdkstays 23).Update blocked store error handling — branch on
BILLING_UNAVAILABLEwith the "Play Store is blocked" debug message instead of genericERROR.Handle nullable
getLinkUri()if you use developer provided billing (null + empty string guard beforeUri.parse).Optionally adopt in app price increase messaging via
showInAppMessages.
Phase 4: Verify
- Clean compile at v8, then at v9. Two checkpoints.
./gradlew clean assembleDebugeach time. - Unit tests at both checkpoints (
./gradlew test). Fix tests that stub old listener shapes or proration constants. - Lifecycle tests on a real device with the latest Play Store: new subscription, renewal, upgrade/downgrade with
ReplacementMode, one time product purchase and consume, pending purchase completion. - License test accounts — repeat without real charges. Pay special attention to plan changes; the proration to replacement rename is the riskiest site.
- Auto reconnection smoke test — sever the connection (airplane mode / force stop Play Store) and confirm reconnection without a manual
startConnection(). - v9 specific checks — blocked store returns
BILLING_UNAVAILABLE; nullable link URI does not crash; manifests merge attargetSdk = 35. - Internal test track rollout — monitor crash reports and billing success rates for at least one full billing cycle before promoting to production.
Report back a final summary: the old version, the two stage path taken, every rename applied in Stage A, the v9 adjustments in Stage B, and the verification evidence.
References
- Full chapter
- Google Play Billing Library v9 migration guide
- Play Billing Library release notes
- RevenueCat Play Billing v9 deep dive
- Upstream deadlines: PBL 7 last accepts new app and update submissions on August 31, 2026; PBL 8 on August 31, 2027. Migrate to v9 with margin to monitor at least one billing cycle.