Uh oh!
There was an error while loading. Please reload this page.
Move Android in-app purchase to the Play Billing ProductDetails API - #5651
Conversation
Reported through support: an app adding Play Billing 9.1.0 got a wall of "cannot find symbol" errors naming com.codename1.impl.android.BillingSupport, a file the developer never wrote. That file was written against the SKU API, which Play Billing removed. Compiling it against every published release places the break exactly at 8.0.0, and 8.0.0 is not a version anyone can avoid: Google's deprecation FAQ states that from Aug 31 2026 all new apps and updates must use billing 8 or later, with an extension to Nov 1 2026. Every Codename One app using in-app purchase on Android was therefore unable to ship a Play-accepted update. billing 4.0.0 .. 7.1.1 0 errors before this change, does not compile after billing 8.0.0, 9.1.0 24 errors before this change, 0 after Six separate removals had to be answered, not just the SkuDetails one the error names: the no-argument enablePendingPurchases(), queryPurchasesAsync(String,..), querySkuDetailsAsync at five call sites, BillingClient.SkuType, BillingFlowParams.setSkuDetails, and the SkuDetails types themselves. Billing 8 and 9 share the modern shape, so one implementation serves both. Two things the ProductDetails API has no direct equivalent for, decided rather than guessed: - There is no single price. A one-time product carries one offer; a subscription carries offers that carry pricing phases. The old SkuDetails.getPrice() is mapped to the one-time offer's formatted price, or a subscription's first phase of its first offer, and to null when Play sends neither -- so a caller shows no price rather than a wrong one. - A subscription is bought through an offer, and launchBillingFlow rejects one with no offer token, while a one-time product must not carry a token it did not ask for. Both cases are handled, and a subscription with no purchasable offer now reports itemPurchaseError instead of silently doing nothing. The builder half, in both this repo and the daemon: the default android.billingclient.version moves from 4.0.0 to 8.0.0, a version below the floor is refused with a sentence naming the hint and the way out rather than letting javac fail in the generated project, and minSdk is raised to whatever the selected billing AAR declares. That last one is not decoration -- the AAR manifests require 21 at 8.0.0 and 23 from 8.1.0 onward, and the manifest merge fails outright against a lower value. It is resolved before the manifest is written, not beside the gradle dependency it feeds; resolving it late put the raised floor in build.gradle and left the manifest saying 19, which is the merge failure it was meant to prevent. The port module no longer compiles BillingSupport, and drops the dead android-billing-4.0.0.jar it compiled against. It ships as source and compiles inside the app build, like the four packages already excluded there for the same reason. That removes the last compile that touched this file, so the check moves to a test that names the removed API and fails if any of it comes back -- weaker than a compile, and it says so, but it is the difference between catching this in CI and hearing about it from a support conversation. It caught one leftover already: loadSkuDetailsAsync kept its old name. Nothing here has been exercised against a real purchase. The API shapes are verified against the published AARs and the file compiles clean against 8.0.0 and 9.1.0, but an actual buy, acknowledge and consume against a Play test account still needs a device pass before this ships. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:971c6d2795
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
… maven The BuildDaemon CI builds this port with ant against the companion CodenameOne branch, so excluding BillingSupport in maven/android/pom.xml alone left that build compiling the ProductDetails source against the billing 4.0.0 jar and failing on the imports. Three places compile this port and each one's comment says it mirrors the other two; only one of them had been updated. The now-unused android-billing-4.0.0.jar goes from the NetBeans classpath with it, matching the pom. A check reads all three files, because "mirrors the other two" written in three comments is not a mechanism, and the one that drifted was found by CI rather than by anything here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
…r taking the low minSdk Two review findings on the billing change. A one-time product can carry offers now, not just subscriptions. When it does, launchBillingFlow has to be told which offer is being bought or it rejects the flow -- the same rule subscriptions have always had, which is why the helper was called subscriptionOfferToken and returned null for everything else. It reads both kinds now: getOneTimePurchaseOfferDetails for the default offer, getOneTimePurchaseOfferDetailsList when Play sends no default. Both members are present from billing 8.0.0, so this still compiles from one source against 8.0.0, 8.3.0 and 9.1.0 -- checked, 0 errors on each. A token is passed only when Play actually supplied one, so a product with no offers still launches with no token exactly as before. The default offer is preferred over the list because it is the one the removed setSkuDetails call would have bought. Separately, android.billingclient.version accepts a Gradle dynamic selector, and the generated dependency keeps it: "8.+" resolves to 8.3.0 at build time while its numeric prefix reads as "8". The range check answered that with the 8.0.0-only floor of 21, and the manifest merge then failed against the library's own 23 -- the exact failure the minSdk raise exists to prevent. 8.0.0 is now matched exactly, so a selector, a range and an unseen version all take the high floor, and 8.0.0 itself still takes 21. Neither is reachable by the existing tests, so both got cases, including one pinning that the exact match did not simply collapse every version to 23. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloudflare Preview
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:60f93f5465
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
The offer-token fix left the price and the purchase reading different offers: the price came from the default offer while the flow launched the first tokenized entry from the list, so a product configured with offers and no base showed one price and charged another -- or showed no price at all while buying happily. One selection now answers both. The default offer still wins when Play sends one, because that is what buying with no token selects and what the removed setSkuDetails call would have bought; the list decides only when there is no default, and then the same entry supplies the price and the token. Still compiles clean against billing 8.0.0 and 9.1.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Uh oh!
There was an error while loading. Please reload this page.
Compared 181 screenshots: 181 matched. |
Compared 160 screenshots: 160 matched. Benchmark Results
Detailed Performance Metrics
|
Compared 217 screenshots: 217 matched. |
Compared 144 screenshots: 144 matched. |
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
Split out of the Kotlin-alignment PR (#5649) — different bug, same support conversation.
What broke
A customer added Play Billing 9.1.0 and got a wall of
cannot find symbolerrors namingcom.codename1.impl.android.BillingSupport— a file they never wrote. Our Android port's billing implementation is written against the SKU API, which Play Billing removed.Compiling
Ports/Android/src/com/codename1/impl/android/BillingSupport.javaagainst each published release (android-34android.jar+ our core jar), counting only errors in that file:This is not optional. Google's deprecation FAQ states verbatim: "By Aug 31, 2026, all new apps and updates to existing apps must use Billing Library version 8 or later." That date has passed; the extension runs to Nov 1, 2026. Every Codename One app using in-app purchase on Android is currently unable to ship a Play-accepted update. The builder's default was
4.0.0, rejected since August 2024.Six removals, not one
SkuDetailsis just the loudest:enablePendingPurchases()no-argqueryPurchasesAsync(String, listener)querySkuDetailsAsync×5 call sitesSkuDetails/SkuDetailsParams/SkuDetailsResponseListenerBillingClient.SkuTypeBillingFlowParams.Builder.setSkuDetailsBilling 8 and 9 share the modern shape (
QueryProductDetailsResult,PendingPurchasesParams), so one implementation serves both.Two judgement calls
SkuDetails.getPrice()maps to the one-time offer's formatted price, or a subscription's first phase of its first offer — and tonullwhen Play sends neither, so a caller shows no price rather than a wrong one.launchBillingFlowrejects a subscription with no offer token, and a one-time product must not carry one. Both handled; a subscription with no purchasable offer now reportsitemPurchaseErrorinstead of silently doing nothing.Builder half
android.billingclient.versiondefault4.0.0→8.0.0(lowest Play still accepts, and the only version at that level whose AAR is content withminSdkVersion21 — every later release needs 23).minSdkraised to whatever the selected AAR declares, matching the existing Android Auto precedent. Resolved before the manifest is written — resolving it beside the gradle dependency put the raised floor inbuild.gradleand left the manifest saying 19, which is the merge failure the raise exists to prevent.The missing gate
maven/androidcompiled this file againstandroid-billing-4.0.0.jarunder-Pcompile-android— which is why it could rot while staying green. It now ships as source only (excluded like the four packages already excluded there for the same reason) and the dead jar dependency is dropped.That removes the last compile touching the file, so the check becomes a test naming the removed API. It's weaker than a compile and says so in its own comment — but it caught a real leftover immediately (
loadSkuDetailsAsynchad kept its old name).Verification
BillingSupport.javacompiles clean against 8.0.0 and 9.1.0; every API shape checked withjavapagainst both AARs.mvn -Pcompile-android -pl android package— BUILD SUCCESS; confirmed noBillingSupport.classis produced and the.javastill ships in the port jar.PlayBillingVersionsTest— 7 cases; minSdk mapping read from the published AAR manifests (21 at 8.0.0, 23 from 8.1.0 on).codenameone-maven-pluginsuite 1854/0; SpotBugs 0 onandroidandcodenameone-maven-plugin; build-hint catalog, render, Vale, LanguageTool and control-character gates all clean.Not verified
No real purchase has been exercised. The shapes are right and it compiles, but an actual buy / acknowledge / consume against a Play test account needs a device pass before this ships.
Companion: codenameone/BuildDaemon#PLACEHOLDER