fix(sdk): preserve fetch products all results#193
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough
ChangesMixed all-query result handling
Onside iOS queue and serialization updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces support for fetching all products and subscriptions combined via a new FetchProductsResultAll result type and ProductOrSubscription model. It updates the Android and iOS implementations across expo-iap, flutter_inapp_purchase, kmp-iap, and openiap packages to handle ProductQueryType.All by mapping products and subscriptions into their respective ProductOrSubscription wrappers. There are no review comments provided, so I have no feedback to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1271-1295: The mixed decode path in
convertAnyListToProductOrSubscriptions is too broad because the outer catch
returns emptyList() and drops all valid items when a single element fails.
Tighten the error handling so one bad bridge payload only skips that item while
preserving the rest of the list, and keep the fallback flow intact
(ProductOrSubscription.fromJson first, then type-based recovery via
convertAnyListToProductSubscriptions or convertAnyListToProducts). Add a brief
comment in this function explaining the decode order and the platform-specific
fallback behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: baf07d7f-c453-49a7-b761-2eb664416562
📒 Files selected for processing (5)
libraries/expo-iap/android/src/main/java/expo/modules/iap/ExpoIapModule.ktlibraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.ktlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
|
Device-backed verification run on 2026-06-25 KST for #193. No dedicated Passed:
Blocked / not runnable in this local environment:
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for querying all product types simultaneously (ProductQueryType.All) by adding the FetchProductsResultAll result type and mapping it across Expo, Flutter, KMP (Android/iOS), and Google OpenIAP modules. Feedback on the changes includes a suggestion to use case-insensitive comparison when parsing product types from raw native payloads on iOS, and a recommendation to resolve a platform behavior discrepancy where Android and iOS default to different query types when params.type is null.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt (1)
840-850: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the mixed
Allunion wrapping.This branch is the regression-sensitive contract:
allmust preserve distinct product vs subscription variants. A short comment makes that intent harder to accidentally collapse later. As per coding guidelines, “Comment complex logic, especially platform-specific code.”Proposed comment
ProductQueryType.Subs -> FetchProductsResultSubscriptions(subsDetails.mapNotNull { it.toSubscriptionProduct() }) ProductQueryType.All -> { + // Preserve the mixed OpenIAP `all` union by wrapping in-app + // products and subscriptions in their distinct variants. val combined = buildList<ProductOrSubscription> { addAll(inAppDetails.map { ProductOrSubscription.ProductItem(it.toProduct()) }) addAll(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt` around lines 840 - 850, Add a short inline comment around the combined list creation in InAppPurchaseAndroid’s product fetch flow to document that FetchProductsResultAll must preserve the distinct ProductItem and ProductSubscriptionItem variants. Keep the note near the buildList/combine step so future changes in this branch do not flatten the mixed union contract.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 453-454: The transaction payload is using the current time instead
of the purchase’s actual timestamp, so the serialized transactionDate changes on
every emission. Update OnsideIapModule’s transaction serialization logic to use
the transaction’s original date field when building the dictionary, and only
default to Date() if that source date is unavailable. Keep the change localized
to the code path that populates transactionDate so getAvailableItems and any
repeated returns preserve the same value.
- Line 249: The transaction cleanup in OnsideIapModule’s purchase flow is still
mutating the payment queue off the main actor. Update the same async path that
reads Onside.defaultPaymentQueue().transactions and ensure the finishTransaction
call is also executed inside MainActor.run, using the existing
Onside.defaultPaymentQueue() and finishTransaction symbols so the read and
mutation stay consistently main-actor isolated.
In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`:
- Line 780: `OpenIapModule` is defaulting `params.type` to
`ProductQueryType.All`, which conflicts with the KMP Android path in
`InAppPurchaseAndroid` and the documented `OpenIapStore` behavior; update the
default in both `OpenIapModule` variants (play and horizon) so the `queryType`
fallback uses `ProductQueryType.InApp` when `params.type` is omitted, keeping
product queries consistent across the Android implementations.
---
Nitpick comments:
In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`:
- Around line 840-850: Add a short inline comment around the combined list
creation in InAppPurchaseAndroid’s product fetch flow to document that
FetchProductsResultAll must preserve the distinct ProductItem and
ProductSubscriptionItem variants. Keep the note near the buildList/combine step
so future changes in this branch do not flatten the mixed union contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f37914cd-cbab-4231-ba80-f5317175d3b9
📒 Files selected for processing (3)
libraries/expo-iap/ios/onside/OnsideIapModule.swiftlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
There was a problem hiding this comment.
Code Review
This pull request introduces support for the FetchProductsResultAll result type (representing a union of products and subscriptions) across several platform modules, and changes the default product query type from All to InApp when unspecified. Additionally, the iOS OnsideIapModule is updated to run store-related calls on the MainActor and caches transaction dates to maintain stability. The review feedback suggests avoiding potential floating-point precision issues in OnsideIapModule.swift by initializing NSDecimalNumber with a Decimal instead of a raw floating-point value.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
libraries/expo-iap/ios/onside/OnsideIapModule.swift (1)
303-314: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHonor
alsoPublishbefore returning available items.
getAvailablePurchases({ alsoPublishToEventListenerIOS: true })forwards this flag into this method, but the returned purchases are never emitted to the purchase listener. Publish each serialized payload whenalsoPublishis true, or explicitly document that Onside does not support this option.🐛 Proposed fix
let payload: [[String: Any]] = try await MainActor.run { try Onside.defaultPaymentQueue().transactions.compactMap { transaction -> [String: Any]? in switch transaction.transactionState { case .purchased, .restored: return try serialize(transaction: transaction) default: return nil } } } + if alsoPublish { + payload.forEach { + sendEvent(OnsideEvent.purchaseUpdated.rawValue, $0) + } + } ExpoIapLog.result("getAvailableItemsOnside", value: payload) return payload🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift` around lines 303 - 314, The getAvailableItemsOnside path ignores the alsoPublish flag even though getAvailablePurchases forwards it here, so update this method to honor that option by emitting each serialized transaction payload to the purchase listener before returning it, or otherwise make the Onside-specific behavior explicit. Use the existing getAvailableItemsOnside flow, the serialize(transaction:) helper, and the purchase-listener publishing code path to locate where the publish call should be added.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 303-314: The getAvailableItemsOnside path ignores the alsoPublish
flag even though getAvailablePurchases forwards it here, so update this method
to honor that option by emitting each serialized transaction payload to the
purchase listener before returning it, or otherwise make the Onside-specific
behavior explicit. Use the existing getAvailableItemsOnside flow, the
serialize(transaction:) helper, and the purchase-listener publishing code path
to locate where the publish call should be added.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 45fecb9a-124f-4463-b9bd-64a78722061d
📒 Files selected for processing (4)
libraries/expo-iap/ios/onside/OnsideIapModule.swiftlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
|
Fixed in 01d46ef. Addressed CodeRabbit's outside-diff caution for Onside getAvailableItems:
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for FetchProductsResultAll across multiple platforms (Android, iOS, Flutter, and KMP) to preserve mixed in-app products and subscriptions in a single query, while updating the default query type fallback to ProductQueryType.InApp. On iOS, it refactors OnsideIapModule to run UI and payment queue operations on the MainActor and implements a transaction date cache. The review feedback suggests avoiding redundant Decimal conversions for NSDecimalNumber to prevent precision loss and dispatching events inside the MainActor.run block to ensure strict concurrency safety.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Summary
fetchProducts({ type: 'all' })empty-result regression by preserving theFetchProductsResultAllmixed product/subscription union at the Expo Android bridge boundary.queryHandlers.fetchProductscompatibility layer soProductQueryType.AllreturnsFetchProductsResultAllinstead of dropping subscriptions through the product-only branch.InAppwhile preserving explicitAllbehavior.alsoPublishevent publishing, and decimal-safe price serialization.fetchProducts(type: 'all'), correct Flutter enum/generic examples, and add post-release notes for the expected patch releases.allresult and did not need code changes.Closes #192
Scope note
Although #192 reproduces through the Expo Android example, the bug is a shared SDK contract issue around
FetchProductsResultAll. This PR keeps the fix in one branch because each touched adapter is part of the same fetch-products parity surface; splitting only the Expo Android line would leave the same contract mismatch in sibling libraries.Release note scope
Assuming this PR is released after merge, the docs release note records the expected package patch releases as:
openiap-google 2.2.3expo-iap 4.3.4flutter_inapp_purchase 9.3.5kmp-iap 2.3.3The OpenIAP Spec remains
2.0.2. Apple, React Native, Godot, and MAUI are documented as audited/unchanged for this regression.Test plan
cd libraries/expo-iap && bun run lint:tsccd libraries/expo-iap && bun run lintcd libraries/expo-iap && bun run testcd libraries/expo-iap/example && bun run testcd libraries/expo-iap/example/android && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :expo-iap:compileDebugKotlincd packages/google && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :openiap:compilePlayDebugKotlincd packages/google && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :openiap:compileHorizonDebugKotlincd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:compileDebugKotlinAndroidcd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:testDebugUnitTestcd libraries/kmp-iap && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew :library:compileKotlinIosSimulatorArm64xcodebuild -workspace libraries/expo-iap/example/ios/expoiapexample.xcworkspace -scheme expoiapexample -configuration Debug -destination 'id=0ED50699-1016-4DFE-8DAF-6BB29AD7FAF1' buildHT79F1A00473): product listing, function calls, and real Google Play purchase path verified. Amazon device was intentionally skipped because it is outside this PR.bun run audit:paritycd libraries/flutter_inapp_purchase && PATH="$HOME/.cache/codex/flutter-3.41.9/bin:$PATH" flutter pub get && flutter analyze && flutter testcd libraries/flutter_inapp_purchase/example && PATH="$HOME/.cache/codex/flutter-3.41.9/bin:$PATH" flutter pub get && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" flutter build apk --debugcd libraries/flutter_inapp_purchase/android && ANDROID_HOME="$HOME/Library/Android/sdk" ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" ./gradlew compileDebugKotlin --no-daemon --stacktraceis not a representative standalone check without a Flutter host project; it fails before this patch on missingio.flutter.*embedding classes. Android compile coverage is provided by the Flutter example host build above.cd packages/docs && PATH="$HOME/.bun/bin:$PATH" bun run lintPATH="$HOME/.bun/bin:$PATH" bun audit:docsgit diff --checkPreview recording: not applicable; this is a native bridge/result mapping and documentation/release-note fix with no visual UI surface.
Summary by CodeRabbit