feat(openiap): apply integration and api updates#545
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Caution Review failedThe pull request is closed. WalkthroughMigrates iOS native integration to OpenIAP with many new iOS-specific APIs/events, removes deprecated Android/Dart fields/aliases, adds a pre-commit hook and README dev docs, introduces ephemeral iOS LLDB helpers and Podfile/podspec updates, and updates tests and examples to match renamed methods. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant App as Flutter app (Dart)
participant Lib as flutter_inapp_purchase (Dart)
participant Channel as Method/Event Channels
participant iOS as iOS native (OpenIAP)
Dev->>App: user action (fetch/purchase/restore)
App->>Lib: call public API (fetchProducts / requestSubscription / restorePurchases / getStorefrontIOS / ...)
Lib->>Channel: invoke method (fetchProducts / requestPurchase / getStorefrontIOS / ...)
Channel->>iOS: native method invoked (handled by OpenIAP)
iOS-->>Channel: returns result or emits event (purchase-updated, iap-promoted-product)
Channel-->>Lib: result or event
alt event == purchase-updated
Lib->>App: dispatch purchase-updated payload
else event == iap-promoted-product
Lib->>App: dispatch promoted product payload
end
Note right of iOS: OpenIAP manages StoreKit details, listeners, and receipt/transaction operations
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests
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.
Summary of Changes
Hello @hyochan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a substantial refactor of the iOS in-app purchase implementation by integrating the OpenIAP module, aiming to modernize the codebase and standardize the API surface across platforms. It streamlines the interaction with native purchase mechanisms, cleans up outdated parameters and fields, and refines the handling of purchase events and historical data within the example application.
Highlights
- iOS Migration to OpenIAP: The core iOS in-app purchase logic has been migrated from direct StoreKit 2 usage to the
OpenIAPmodule. This involves significant changes to how products are fetched, purchases are made, and transactions are handled, leveraging OpenIAP's abstractions. - API Alignment and Deprecation Cleanup: Several iOS-specific API methods have been renamed (e.g.,
getItemstofetchProducts,getStorefronttogetStorefrontIOS) and aligned with the new OpenIAP integration. Deprecated fields and parameters related to Android subscription offers and proration modes have been removed from the codebase. - Enhanced Purchase History Handling: The example application and underlying iOS logic for
getAvailablePurchaseshave been updated to explicitly differentiate between active (non-consumed) and historical (including expired) purchases, providing more granular control and clarity. - Improved Event Handling: The iOS plugin now sets up OpenIAP listeners for purchase updates, errors, and promoted products earlier in the lifecycle, ensuring more robust and timely event propagation to the Dart side, with improved type coercion for compatibility.
- Build System Updates: The iOS project's
Podfile.lockandRunner.xcodeproj/project.pbxprojhave been updated to include theopeniapCocoaPods dependency, and the minimum iOS deployment target inexample/ios/Flutter/Flutter.podspechas been adjusted.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request successfully migrates the iOS implementation to use the OpenIAP native module, aligning with the project's goal of standardization. The changes also include valuable cleanup of deprecated APIs on both Android and Dart sides, improving code health. The refactoring on the Swift side is extensive and well-executed. My review includes a couple of minor suggestions to fix an inconsistency in the example app's configuration and to clean up some log messages in the native iOS code.
There was a problem hiding this comment.
Codex Review: Here are some suggestions.
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, or 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 fix this CI failure" or "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/types.dart (1)
3-5: Fix export directive; add missing semicolon and avoid mis-export.Compilation will fail due to the missing semicolon. Also, exporting
getCurrentPlatformfromerrors.dartis likely wrong (it should come fromenums.dart, which you already export). Simplify to export only whaterrors.dartowns.Apply:
-export 'enums.dart'; -export 'errors.dart' - show PurchaseError, PurchaseResult, ConnectionResult, getCurrentPlatform; +export 'enums.dart'; +export 'errors.dart' + show PurchaseError, PurchaseResult, ConnectionResult;
♻️ Duplicate comments (4)
example/ios/Flutter/Flutter.podspec (1)
14-14: Align iOS deployment target to 15.0 (matches plugin + README).Set to 15.0 to avoid mismatches and SK2/OpenIAP runtime issues in the example app.
- s.ios.deployment_target = '13.0' + s.ios.deployment_target = '15.0'ios/Classes/FlutterInappPurchasePlugin.swift (2)
193-193: Remove unnecessary backslashes from string interpolation in log statements.The backslashes before the string interpolation are unnecessary and will be printed literally in the logs, making them harder to read.
Apply this diff to fix the log statements:
- print("\\(FlutterInappPurchasePlugin.TAG) ✅ purchaseUpdatedListener fired") + print("\(FlutterInappPurchasePlugin.TAG) ✅ purchaseUpdatedListener fired") - print("\\(FlutterInappPurchasePlugin.TAG) Emitting purchase-updated to Flutter") + print("\(FlutterInappPurchasePlugin.TAG) Emitting purchase-updated to Flutter") - print("\\(FlutterInappPurchasePlugin.TAG) ❌ purchaseErrorListener fired") + print("\(FlutterInappPurchasePlugin.TAG) ❌ purchaseErrorListener fired") - print("\\(FlutterInappPurchasePlugin.TAG) Emitting purchase-error to Flutter") + print("\(FlutterInappPurchasePlugin.TAG) Emitting purchase-error to Flutter") - print("\\(FlutterInappPurchasePlugin.TAG) 📱 promotedProductListenerIOS fired for: \(productId)") + print("\(FlutterInappPurchasePlugin.TAG) 📱 promotedProductListenerIOS fired for: \(productId)")Also applies to: 203-203, 212-212, 221-221, 230-230
497-521: Verify the return type of getPromotedProductIOS matches the Dart API expectations.The iOS implementation now returns a structured map with product details (productIdentifier, title, description, price, etc.), but the Dart wrapper still declares
getPromotedProduct()asFuture<String?>and simply forwards the native call. This type mismatch will cause runtime errors when existing code tries to use the result as a String.#!/bin/bash # Check the Dart API declaration for getPromotedProduct echo "Checking Dart API declaration for getPromotedProduct:" rg -n "Future<.*?>\s+getPromotedProduct\(" lib/lib/flutter_inapp_purchase.dart (1)
2020-2026: Critical: getPromotedProduct return type mismatch will cause runtime errors.The Dart method still returns
Future<String?>but the iOS native implementation now returns a structured map containing product details. This type mismatch will cause runtimeTypeErrorwhen callers try to use the result as a String.Either:
- Update the Dart method signature to return
Future<Map<String, dynamic>?>and document the breaking change, or- Modify the iOS implementation to extract just the
productIdentifierstring from the mapOption 1 (Update Dart API - breaking change):
- Future<String?> getPromotedProduct() async { + Future<Map<String, dynamic>?> getPromotedProduct() async { if (_platform.isIOS) { - return await _channel.invokeMethod('getPromotedProductIOS'); + final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('getPromotedProductIOS'); + return result != null ? Map<String, dynamic>.from(result) : null; } return null; }Option 2 (Keep String API - non-breaking):
Future<String?> getPromotedProduct() async { if (_platform.isIOS) { - return await _channel.invokeMethod('getPromotedProductIOS'); + final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('getPromotedProductIOS'); + return result?['productIdentifier'] as String?; } return null; }
🧹 Nitpick comments (5)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)
7-33: Generated file triggers ARG001; exclude ephemeral from lint.Prefer repo-level linter ignore (e.g., .ruffignore) for
example/ios/Flutter/ephemeral/**rather than editing generated code.lib/types.dart (4)
511-532: Unreachable branch:this is SubscriptioninsideProduct.toJson.
Subscriptiondoes not extendProduct; this branch is dead code and confusing. Remove it to reduce maintenance burden.- // Add OpenIAP compliant iOS fields for Subscription - // Note: In Product class, this check is needed; in Subscription class, it's redundant - else if (this is Subscription) { - // Remove unnecessary cast since we know the type - if ((this as dynamic).isFamilyShareableIOS != null) { - json['isFamilyShareableIOS'] = (this as dynamic).isFamilyShareableIOS; - } - if ((this as dynamic).jsonRepresentationIOS != null) { - json['jsonRepresentationIOS'] = - (this as dynamic).jsonRepresentationIOS; - } - } + // (removed unreachable Subscription branch)
1200-1219: Constructor forwardssubscriptionOfferDetailsAndroidbutfromJsonnever sets it. Verify intent.
ProductAndroidaccepts and forwardssuper.subscriptionOfferDetailsAndroid, yetProductAndroid.fromJsonomits it (a comment later says it exists only onProduct). If consumers constructProductAndroidviafromJson, the field will always be null, diverging fromProduct.fromJson.If you want parity with
Product.fromJson, wire it here too:factory ProductAndroid.fromJson(Map<String, dynamic> json) { return ProductAndroid( productId: json['productId'] as String? ?? '', price: json['price'] as String? ?? '0', currency: json['currency'] as String?, localizedPrice: json['localizedPrice'] as String?, title: json['title'] as String?, description: json['description'] as String?, type: json['type'] as String?, originalPrice: json['originalPrice'] as String?, + subscriptionOfferDetailsAndroid: json['subscriptionOfferDetailsAndroid'] != null + ? (json['subscriptionOfferDetailsAndroid'] as List) + .map((item) { + final map = _safeJsonMap(item); + return map != null ? OfferDetail.fromJson(map) : null; + }) + .whereType<OfferDetail>() + .toList() + : null, subscriptionPeriod: json['subscriptionPeriod'] as String?, introductoryPriceCycles: json['introductoryPriceCycles'] as String?, introductoryPricePeriod: json['introductoryPricePeriod'] as String?, freeTrialPeriod: json['freeTrialPeriod'] as String?, signature: json['signature'] as String?, subscriptionOffers: json['subscriptionOffers'] != null
1244-1247: Comment contradicts constructor API.The note says
subscriptionOfferDetailsAndroidis only inProduct, but the constructor explicitly forwards it. Either parse it infromJson(preferred) or drop the forwarded param to avoid confusion.
1811-1853: Deduplicate and fix key casing inPurchase.toString(iOS block).
productTypeIOSandsubscriptionGroupIdIOSare printed twice.- Mixed casing for storefront:
storefrontCountryCodeIOSvsstoreFrontCountryCodeIOS.- if (subscriptionGroupIdIOS != null) { - buffer.writeln(' subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",'); - } - if (productTypeIOS != null) { - buffer.writeln(' productTypeIOS: "$productTypeIOS",'); - } + if (subscriptionGroupIdIOS != null) { + buffer.writeln(' storeFrontCountryCodeIOS: "$storeFrontCountryCodeIOS",'); + } if (transactionReasonIOS != null) { buffer.writeln(' transactionReasonIOS: "$transactionReasonIOS",'); } if (currencyCodeIOS != null) { buffer.writeln(' currencyCodeIOS: "$currencyCodeIOS",'); } if (storeFrontCountryCodeIOS != null) { - buffer.writeln( - ' storefrontCountryCodeIOS: "$storeFrontCountryCodeIOS",', - ); + buffer.writeln(' storeFrontCountryCodeIOS: "$storeFrontCountryCodeIOS",'); } ... - if (productTypeIOS != null) { - buffer.writeln(' productTypeIOS: "$productTypeIOS",'); - } - if (subscriptionGroupIdIOS != null) { - buffer.writeln(' subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",'); - } - if (storeFrontCountryCodeIOS != null) { - buffer.writeln( - ' storeFrontCountryCodeIOS: $storeFrontCountryCodeIOS,', - ); - } + // (removed duplicate prints for productTypeIOS, subscriptionGroupIdIOS, storeFrontCountryCodeIOS)Also keep only one occurrence of each field and use the correct
storeFrontCountryCodeIOScasing consistently.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
example/ios/Podfile.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
README.md(1 hunks)android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt(2 hunks)example/ios/Flutter/Flutter.podspec(1 hunks)example/ios/Flutter/ephemeral/flutter_lldb_helper.py(1 hunks)example/ios/Flutter/ephemeral/flutter_lldbinit(1 hunks)example/ios/Runner.xcodeproj/project.pbxproj(1 hunks)example/lib/src/screens/available_purchases_screen.dart(1 hunks)ios/Classes/FlutterInappPurchasePlugin.swift(11 hunks)ios/flutter_inapp_purchase.podspec(1 hunks)lib/enums.dart(0 hunks)lib/flutter_inapp_purchase.dart(11 hunks)lib/modules/ios.dart(2 hunks)lib/types.dart(5 hunks)
💤 Files with no reviewable changes (1)
- lib/enums.dart
🧰 Additional context used
📓 Path-based instructions (8)
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using emojis in documentation, especially in headings
Files:
README.md
{README.md,docs/**}
📄 CodeRabbit inference engine (GEMINI.md)
{README.md,docs/**}: Update relevant documentation when API changes are made
Document new features in README or API docs
Files:
README.md
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
lib/modules/ios.dartexample/lib/src/screens/available_purchases_screen.dartlib/flutter_inapp_purchase.dartlib/types.dart
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/modules/ios.dartlib/flutter_inapp_purchase.dartlib/types.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
lib/modules/ios.dartandroid/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dartlib/types.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
lib/modules/ios.dartandroid/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktexample/lib/src/screens/available_purchases_screen.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dartlib/types.dart
example/**
📄 CodeRabbit inference engine (GEMINI.md)
Update the example app when needed for new features
Files:
example/ios/Runner.xcodeproj/project.pbxprojexample/ios/Flutter/ephemeral/flutter_lldbinitexample/ios/Flutter/ephemeral/flutter_lldb_helper.pyexample/lib/src/screens/available_purchases_screen.dartexample/ios/Flutter/Flutter.podspec
{android/**,ios/**}
📄 CodeRabbit inference engine (GEMINI.md)
For new features, implement for both iOS and Android when applicable
Files:
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktios/flutter_inapp_purchase.podspecios/Classes/FlutterInappPurchasePlugin.swift
🧠 Learnings (13)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Document new features in README or API docs
Applied to files:
README.md
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Update relevant documentation when API changes are made
Applied to files:
README.md
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
lib/modules/ios.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Applied to files:
lib/modules/ios.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
lib/flutter_inapp_purchase.dartlib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
Applied to files:
lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use RequestProductsParams; it has been removed
Applied to files:
lib/types.dart
🪛 Ruff (0.12.2)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py
7-7: Unused function argument: bp_loc
(ARG001)
7-7: Unused function argument: extra_args
(ARG001)
7-7: Unused function argument: intern_dict
(ARG001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (17)
ios/flutter_inapp_purchase.podspec (1)
17-19: OpenIAP dependency addition looks good.Please confirm
openiap ~> 1.1.6resolves via CocoaPods and works on CI/macOS runners.lib/modules/ios.dart (2)
102-112: Use of IOS-suffixed channel method is correct; verify native handler name.Ensure the Swift side exports
presentCodeRedemptionSheetIOS(exact string) on the MethodChannel.
114-124: Consistent IOS-suffixed API; verify native handler name.Ensure iOS plugin exposes
showManageSubscriptionsIOSon the MethodChannel.android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (1)
838-839: EOF newline added.No concerns.
README.md (1)
54-59: iOS notes are clear and consistent with the migration.Please also align example’s Flutter.podspec to iOS 15.0 (it’s currently 13.0) to match this doc and the plugin podspec.
example/ios/Runner.xcodeproj/project.pbxproj (1)
287-295: OpenIAP.framework embed entries look fine.Confirm these entries are generated by CocoaPods (not hand-edited) by re-running
pod installand committing Pod-related diffs.example/ios/Flutter/ephemeral/flutter_lldbinit (1)
5-5: LLDB init inclusion is fine.No action needed.
ios/Classes/FlutterInappPurchasePlugin.swift (1)
451-464: LGTM!The OpenIAP integration for presentCodeRedemptionSheetIOS is correctly implemented with proper iOS version checking and async/await error handling.
example/lib/src/screens/available_purchases_screen.dart (2)
112-115: LGTM! Correctly implements platform-aware purchase filtering for iOS.The implementation properly uses
PurchaseOptionswithonlyIncludeActiveItemsIOS: trueto filter only active items on iOS, aligning with the OpenIAP specification changes.
122-132: Good platform-specific history handling.The implementation correctly differentiates between iOS and Android for fetching purchase history. iOS uses
getAvailablePurchaseswithonlyIncludeActiveItemsIOS: falseto include expired items, while Android continues using the dedicatedgetPurchaseHistoriesAPI.lib/flutter_inapp_purchase.dart (4)
208-212: LGTM! Correct OpenIAP migration for iOS fetchProducts.The iOS implementation now properly uses
fetchProductswith explicit type parameter, aligning with the OpenIAP module changes.
821-824: Good defensive programming for iOS ProductIOS parsing.The code properly handles both
productIdandidfield names with fallback to empty string, and ensures price and currency are coerced to strings for consistent typing.
1348-1348: LGTM! Correct removal of deprecated prorationModeAndroid parameter.The code properly uses
replacementModeAndroidinstead of the deprecatedprorationModeAndroid, aligning with the Android billing library update.
1981-1983: Verify iOS restore purchase flow
lib/flutter_inapp_purchase.dart:1981-1983
Ensure that usingawait _channel.invokeMethod('endConnection'); await _channel.invokeMethod('initConnection');actually invokes the App Store’s
restoreCompletedTransactionsunder the hood via OpenIAP and does not unregister or lose any active listeners. Confirm in the OpenIAP iOS source or documentation thatinitConnectioncovers restoration and review potential side-effects on listener registration.lib/types.dart (3)
48-51: Nice: makePurchaseOptionsconst.Const constructor here is appropriate for an options value object.
340-350: LGTM: tolerant parsing forsubscriptionOfferDetailsAndroid.The
_safeJsonMap+whereTypepattern is safe and aligns with mixed shapes returned from Android.
820-830: LGTM: same tolerant parsing inSubscription.fromJson.Consistent with
Product.fromJson.
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
ios/Classes/FlutterInappPurchasePlugin.swift (1)
72-87: Preserve iOS purchase options (appAccountToken, quantity) and remove silent drops.Dart may pass 'forUser' (appAccountToken) and 'quantity' when invoking buyProduct. Native currently ignores both. Parse them and forward to OpenIAP props.
@@ - case "buyProduct": - // Support both old and new API - var productId: String? - - if let args = call.arguments as? [String: Any] { - productId = args["productId"] as? String ?? args["sku"] as? String - } else if let id = call.arguments as? String { - productId = id - } - - guard let id = productId else { + case "buyProduct": + // Support both old and new API + var productId: String? + var appAccountToken: String? + var quantity: Int? + + if let args = call.arguments as? [String: Any] { + productId = args["productId"] as? String ?? args["sku"] as? String + if let q = args["quantity"] as? Int { quantity = q } + else if let qs = args["quantity"] as? String { quantity = Int(qs) } + appAccountToken = (args["forUser"] as? String) ?? (args["appAccountToken"] as? String) + } else if let id = call.arguments as? String { + productId = id + } + + guard let id = productId else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "productId required", details: nil)) return } - buyProduct(productId: id, result: result) + buyProduct(productId: id, appAccountToken: appAccountToken, quantity: quantity, result: result) @@ - private func buyProduct(productId: String, result: @escaping FlutterResult) { + private func buyProduct(productId: String, appAccountToken: String?, quantity: Int?, result: @escaping FlutterResult) { print("\(FlutterInappPurchasePlugin.TAG) buyProduct called with productId: \(productId)") Task { @MainActor in do { let props = OpenIapRequestPurchaseProps( sku: productId, andDangerouslyFinishTransactionAutomatically: false, - appAccountToken: nil, - quantity: nil, + appAccountToken: appAccountToken, + quantity: quantity, withOffer: nil ) _ = try await OpenIapModule.shared.requestPurchase(props) result(nil)Also applies to: 351-377
lib/flutter_inapp_purchase.dart (1)
292-316: Avoid calling unimplemented iOS methods (requestProductWithOfferIOS/requestProductWithQuantityIOS).These channel methods don’t exist in the new Swift plugin and will throw. Route all iOS purchases through 'buyProduct' and pass optional fields; native now consumes appAccountToken/quantity.
- if (iosRequest.withOffer != null) { - await _channel - .invokeMethod('requestProductWithOfferIOS', <String, dynamic>{ - 'sku': iosRequest.sku, - 'forUser': iosRequest.appAccountToken ?? '', - 'withOffer': iosRequest.withOffer!.toJson(), - }); - } else if (iosRequest.quantity != null && iosRequest.quantity! > 1) { - await _channel.invokeMethod( - 'requestProductWithQuantityIOS', - <String, dynamic>{ - 'sku': iosRequest.sku, - 'quantity': iosRequest.quantity!.toString(), - }, - ); - } else { - if (type == iap_types.ProductType.subs) { - await requestSubscription(iosRequest.sku); - } else { - await _channel.invokeMethod('buyProduct', <String, dynamic>{ - 'sku': iosRequest.sku, - 'forUser': iosRequest.appAccountToken, - }); - } - } + await _channel.invokeMethod('buyProduct', <String, dynamic>{ + 'sku': iosRequest.sku, + if (iosRequest.appAccountToken != null) + 'forUser': iosRequest.appAccountToken, + if (iosRequest.quantity != null) 'quantity': iosRequest.quantity, + // TODO: wire up withOffer once native supports it + });
♻️ Duplicate comments (2)
example/ios/Flutter/Flutter.podspec (1)
14-14: Good: iOS min target aligned to 15.0 (OpenIAP/StoreKit 2 requirement).This resolves prior inconsistency with README and podspec. No further action.
lib/flutter_inapp_purchase.dart (1)
2021-2030: API now returns a Map for getPromotedProduct — matches native.Resolves prior P1 mismatch (String → Map). Back-compat wrapper for String is a nice touch.
🧹 Nitpick comments (3)
ios/Classes/FlutterInappPurchasePlugin.swift (2)
38-41: Optional: gate canMakePayments behind initialization.Returning true unconditionally may mislead callers before initConnection completes. Consider returning _initialized state via OpenIAP if available, or defer to isReady() on Dart.
129-134: Optional: expose getAppTransaction on iOS or document as best-effort.Dart calls 'getAppTransaction' but iOS plugin has no case. It’s caught and returns null, which is fine. If desired, add a stub handler returning nil to avoid exceptions in logs.
@@ case "showManageSubscriptionsIOS": showManageSubscriptionsIOS(result: result) + case "getAppTransaction": + result(nil) // optional stub until implementedAlso applies to: 523-536
lib/flutter_inapp_purchase.dart (1)
631-643: Keep in sync with native getStorefrontIOS return shape.After changing native to return {'countryCode': ''}, this read is correct. If you prefer native to return a String, adjust here to invokeMethod and return it directly.
- final result = await channel.invokeMethod<Map<dynamic, dynamic>>( - 'getStorefrontIOS', - ); - if (result != null && result['countryCode'] != null) { - return result['countryCode'] as String; - } + // If native returns a raw String instead of a map, use this: + // final result = await channel.invokeMethod<String>('getStorefrontIOS'); + // if (result != null) return result;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt(2 hunks)example/ios/Flutter/Flutter.podspec(1 hunks)ios/Classes/FlutterInappPurchasePlugin.swift(11 hunks)lib/flutter_inapp_purchase.dart(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
🧰 Additional context used
📓 Path-based instructions (6)
example/**
📄 CodeRabbit inference engine (GEMINI.md)
Update the example app when needed for new features
Files:
example/ios/Flutter/Flutter.podspec
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
{android/**,ios/**}
📄 CodeRabbit inference engine (GEMINI.md)
For new features, implement for both iOS and Android when applicable
Files:
ios/Classes/FlutterInappPurchasePlugin.swift
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
lib/flutter_inapp_purchase.dart
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/flutter_inapp_purchase.dart
🧠 Learnings (7)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Implementations must follow the OpenIAP specification (APIs, Types, Events, Errors)
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:01:17.634Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
lib/flutter_inapp_purchase.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
ios/Classes/FlutterInappPurchasePlugin.swift (1)
209-223: Alignpurchase-errorpayload with DartPurchaseResult.fromJSON. Swift is emitting onlycode,messageandproductId, butPurchaseResult.fromJSONexpects keys
–responseCode(int?),
–debugMessage(String?),
–code(String?),
–message(String?),
–purchaseTokenAndroid(String?).Either augment the JSON in ios/Classes/FlutterInappPurchasePlugin.swift to include
responseCodeanddebugMessage(and drop or renameproductIdif it’s unused), or update the Dart model to consumeproductIdif intentional. [ios/Classes/FlutterInappPurchasePlugin.swift:209–223]⛔ Skipped due to learnings
Learnt from: CR PR: hyochan/flutter_inapp_purchase#0 File: CLAUDE.md:0-0 Timestamp: 2025-08-31T19:58:54.628Z Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)Learnt from: CR PR: hyochan/flutter_inapp_purchase#0 File: CLAUDE.md:0-0 Timestamp: 2025-08-31T19:58:54.628Z Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)Learnt from: hyochan PR: hyochan/flutter_inapp_purchase#531 File: docs/versioned_docs/version-6.3/api/index.md:65-69 Timestamp: 2025-08-21T02:25:46.402Z Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.Learnt from: CR PR: hyochan/flutter_inapp_purchase#0 File: CLAUDE.md:0-0 Timestamp: 2025-08-31T19:58:54.628Z Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)Learnt from: hyochan PR: hyochan/flutter_inapp_purchase#531 File: docs/versioned_docs/version-6.3/api/overview.md:128-131 Timestamp: 2025-08-21T01:20:28.525Z Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.Learnt from: hyochan PR: hyochan/flutter_inapp_purchase#531 File: docs/versioned_docs/version-6.3/api/overview.md:128-131 Timestamp: 2025-08-21T01:20:28.525Z Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".Learnt from: CR PR: hyochan/flutter_inapp_purchase#0 File: CLAUDE.md:0-0 Timestamp: 2025-08-31T19:58:54.628Z Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)lib/flutter_inapp_purchase.dart (4)
208-213: Good: iOS now uses fetchProducts with direct parameters (skus/type).Matches OpenIAP naming (“skus”) and guideline to avoid params objects.
688-707: LGTM: iOS wrappers (presentCodeRedemptionSheetIOS/showManageSubscriptionsIOS) and platform-gated calls.Bindings and error mapping look consistent with native.
Also applies to: 671-681, 1992-2006, 686-709
1387-1391: OK: getPendingTransactionsIOS channel name matches native.Decoding path (JSON-encoding list) is consistent with other iOS flows.
1978-1987: Restore on iOS via reconnection – confirm UX.The end/init approach is acceptable with OpenIAP; just be aware it toggles connection state. No change requested.
Would you like to log connection-updated events around this sequence for easier debugging?
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #545 +/- ##
==========================================
+ Coverage 35.71% 37.25% +1.54%
==========================================
Files 10 10
Lines 2212 2158 -54
==========================================
+ Hits 790 804 +14
+ Misses 1422 1354 -68 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
3af2e05 to
48afb4f
Compare
Migrate iOS to OpenIAP module, add fetchProducts, align iOS method names (*IOS), coerce payload types, and update example to use getAvailablePurchases. Remove deprecated TODO v6.4.0 items across Dart/Android. Includes validateReceiptIOS via OpenIAP with purchaseToken and platform in payload.
Reduce Swift-side mapping; rely on Dart parser. Update Dart to coerce iOS product price to string via toString() and fallback productId from id. This aligns closer to Expo module semantics.
- Set example iOS deployment target to 15.0\n- Remove stray backslashes in Swift logs\n- Android: read replacementModeAndroid (fallback to replacementMode)\n- Dart: getPromotedProduct returns structured map for iOS
Adapt tests to new iOS fetchProducts API and expected arguments; keep behavior identical otherwise.
Add tests for presentCodeRedemptionSheetIOS, showManageSubscriptionsIOS, getStorefrontIOS, getPromotedProduct (map), and getPendingTransactionsIOS to raise patch coverage.
Versioned git hook under .githooks runs flutter pub get, dart format check, and flutter test. To enable locally: git config core.hooksPath .githooks
… storefront code
- clearTransactionCache returns success to avoid Dart-side exceptions\n- promoted product event now 'iap-promoted-product' with String payload\n- getStorefrontIOS returns { countryCode } to match Dart expectations
78ab532 to
7e6ab9c
Compare
Replace local error enum with OpenIapError E_* constants from the OpenIAP module and normalize a hardcoded service error. Also remove duplicate definitions of getPromotedProductIOS and showManageSubscriptionsIOS that caused invalid redeclaration build failures. This keeps error codes single‑sourced and restores a clean iOS build.
Make .githooks/pre-commit executable and enhance it to auto-format staged Dart files, re-stage them, and enforce a repo-wide format check before running tests. Prevents CI format failures by catching them locally.
Update pre-commit hook to auto-format staged Dart files, re-stage changes, and verify formatting across the repo. Make the hook executable and document enabling hooks in README (). Prevents CI format failures by catching them before push.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (4)
396-401: Supportskusper OpenIAP; keepproductIdsas fallback.Aligns with current spec and Dart API.
- val productIds : ArrayList<String> = call.argument<ArrayList<String>>("productIds")!! + val productIds: ArrayList<String> = + call.argument<ArrayList<String>>("skus") + ?: call.argument<ArrayList<String>>("productIds") + ?: arrayListOf() + if (productIds.isEmpty()) { + safeChannel.error(call.method, "E_INVALID_ARGS", "Missing 'skus' (or legacy 'productIds').") + return + }
75-77: Possible NPE:channel!!before it's guaranteed set.Guard channel null and return a proper error.
- safeResult = MethodResultWrapper(result, channel!!) - val safeChannel = MethodResultWrapper(result, channel!!) + val ch = channel ?: run { + result.error("E_CHANNEL_NULL", "MethodChannel not attached yet.", null) + return + } + safeResult = MethodResultWrapper(result, ch) + val safeChannel = MethodResultWrapper(result, ch)
191-196: Possible NPE:activity!!in URL open flow.Gracefully handle when activity isn’t attached.
- try{ - activity!!.startActivity(Intent(Intent.ACTION_VIEW).apply { data = uri }) + val act = activity ?: return false + try{ + act.startActivity(Intent(Intent.ACTION_VIEW).apply { data = uri })
242-243: Compile-breaker:Purchase.developerPayloadno longer exists (Billing v8).Remove usage or replace with available identifiers (e.g., obfuscated ids). Leaving as-is will fail to build.
- item.put("developerPayloadAndroid", purchase.developerPayload) + // developerPayload removed in V5+. Omit field to avoid build errors.- item.put("developerPayloadAndroid", purchase.developerPayload) + // developerPayload removed in V5+. Omit field to avoid build errors.- item.put("developerPayloadAndroid", purchase.developerPayload) + // developerPayload removed in V5+. Omit field to avoid build errors.Also applies to: 373-374, 785-785
lib/types.dart (1)
1191-1247: Wire up subscriptionOfferDetailsAndroid in ProductAndroid.fromJson (or drop param)The ctor forwards
super.subscriptionOfferDetailsAndroid, butfromJsonnever sets it. Either parse and pass it, or remove the super-param to avoid confusion.factory ProductAndroid.fromJson(Map<String, dynamic> json) { return ProductAndroid( productId: json['productId'] as String? ?? '', price: json['price'] as String? ?? '0', @@ originalPriceAmount: (json['originalPriceAmount'] as num?)?.toDouble(), iconUrl: json['iconUrl'] as String?, - // Note: subscriptionOfferDetailsAndroid is only in Product class + // Forward Android-only details to the base Product + subscriptionOfferDetailsAndroid: json['subscriptionOfferDetailsAndroid'] != null + ? (json['subscriptionOfferDetailsAndroid'] as List) + .map((item) { + final map = _safeJsonMap(item); + return map != null ? OfferDetail.fromJson(map) : null; + }) + .whereType<OfferDetail>() + .toList() + : null, ); }example/lib/src/screens/available_purchases_screen.dart (1)
135-151: Guard setState after awaits to prevent setState() called after disposerisk: async tasks may complete after widget is disposed. Wrap post-await setState calls with mounted checks.
Apply this diff in both _initConnection and _loadPurchases (repeat pattern wherever setState follows awaits):
try { await _iap.initConnection(); - setState(() { - _connected = true; - }); + if (!mounted) return; + setState(() { + _connected = true; + }); await _loadPurchases(); } catch (e) { - setState(() { + if (!mounted) return; + setState(() { _error = e.toString(); }); } finally { - setState(() { + if (!mounted) return; + setState(() { _loading = false; }); }Also applies to: 79-101
♻️ Duplicate comments (2)
example/ios/Flutter/Flutter.podspec (1)
14-14: Align iOS deployment target to 15.0 (StoreKit 2).Example app should match plugin/docs minimum to avoid build mismatches.
- s.ios.deployment_target = '13.0' + s.ios.deployment_target = '15.0'lib/flutter_inapp_purchase.dart (1)
2034-2041: Breaking change: getPromotedProduct return type changed.The return type changed from
Future<String?>toFuture<Map<String, dynamic>?>. While backward compatibility is attempted with theproductIdentifierwrapper, this is still a breaking change for existing code that expects a string.
🧹 Nitpick comments (9)
README.md (1)
54-59: iOS notes look good—add pod repo update tip and confirm version sync.Add a troubleshooting note and double-check that OpenIAP 1.1.7 matches Podspec/Podfile.
### iOS Notes - This plugin uses the OpenIAP Apple native module via CocoaPods (`openiap ~> 1.1.7`). - After upgrading, run `pod install` in your iOS project (e.g., `example/ios`). - Minimum iOS deployment target is `15.0` for StoreKit 2 support. +- If CocoaPods can't find `openiap (1.1.7)`, run `pod repo update` then `pod install`.docs/docs/api/error-codes.md (2)
92-107: Formatting-only churn.Extra blank lines increase diff noise. Consider reverting or enforcing a docs formatter to keep minimal changes.
599-603: Update snippet to OpenIAP requestProducts + skus.Docs should reflect current API/terminology.
- final products = await FlutterInappPurchase.instance.getProducts([productId]); + final products = await FlutterInappPurchase.instance.requestProducts( + skus: [productId], + type: PurchaseType.inapp, + );example/ios/Podfile (1)
35-36: Pinned OpenIAP via git tag—verify duplication with plugin Podspec.This enforces 1.1.7 for the example, but the plugin also depends on
openiap ~> 1.1.7. Confirm only one Pod is installed and no duplicate embed occurs; remove the direct Pod once trunk reliably has 1.1.7.lib/types.dart (2)
1758-1869: Deduplicate iOS fields in Purchase.toString (several keys printed twice)
productTypeIOS,subscriptionGroupIdIOS,currencyCodeIOS,storeFrontCountryCodeIOS, andexpirationDateIOSare emitted twice (and once with a lowercase “storefront”). Trim the duplicates to keep logs compact and consistent.- if (productTypeIOS != null) { - buffer.writeln(' productTypeIOS: "$productTypeIOS",'); - } @@ - if (subscriptionGroupIdIOS != null) { - buffer.writeln(' subscriptionGroupIdIOS: "$subscriptionGroupIdIOS",'); - } @@ - if (currencyCodeIOS != null) { - buffer.writeln(' currencyCodeIOS: "$currencyCodeIOS",'); - } @@ - if (storeFrontCountryCodeIOS != null) { - buffer.writeln( - ' storefrontCountryCodeIOS: "$storeFrontCountryCodeIOS",', - ); - } @@ - if (expirationDateIOS != null) { - buffer.writeln(' expirationDateIOS: $expirationDateIOS,'); - }
239-274: Defaulting Product.platformEnum to iOS can misclassify platformIf callers omit
platformEnum, Android products may serialize as iOS. Consider defaulting based on an explicitplatformstring or requiring the enum.test/ios_methods_test.dart (1)
85-90: Naming consistency: use IOS suffix for iOS-only Dart APIsTest calls getPromotedProduct(), but other iOS-only APIs use IOS suffix (e.g., getStorefrontIOS, getPendingTransactionsIOS). Consider using getPromotedProductIOS() for consistency, if available, and assert the same channel method.
#!/bin/bash # Find promoted product APIs to confirm available names rg -nP --type=dart -C2 '\bgetPromotedProduct(IOS)?\s*\('lib/errors.dart (1)
103-137: Add a few more user-friendly branches (optional)Consider explicit messages for eIapNotAvailable and eUnknown to improve UX fallbacks.
switch (code) { + case ErrorCode.eIapNotAvailable: + return 'In‑app purchases are not available on this device'; ... default: // Try to surface message from PurchaseError if available if (error is PurchaseError && error.message.isNotEmpty) { return error.message; } if (error is Map && error['message'] is String) { return error['message'] as String; } - return 'An unexpected error occurred'; + return 'An unexpected error occurred. Please try again.'; }ios/Classes/FlutterInappPurchasePlugin.swift (1)
267-275: Add input validation for SKU format.The method handles both dictionary and array formats but doesn't validate the SKU format or content. Consider adding validation to prevent invalid SKUs from being sent to the native layer.
// Add SKU validation for sku in skus { guard !sku.isEmpty && !sku.contains(" ") else { result(FlutterError(code: OpenIapError.E_DEVELOPER_ERROR, message: "Invalid SKU format: '\(sku)'", details: nil)) return } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
example/ios/Podfile.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
.githooks/pre-commit(1 hunks)README.md(1 hunks)android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt(2 hunks)docs/docs/api/error-codes.md(24 hunks)example/ios/Flutter/Flutter.podspec(1 hunks)example/ios/Flutter/ephemeral/flutter_lldb_helper.py(1 hunks)example/ios/Flutter/ephemeral/flutter_lldbinit(1 hunks)example/ios/Podfile(2 hunks)example/ios/Runner.xcodeproj/project.pbxproj(1 hunks)example/lib/src/screens/available_purchases_screen.dart(1 hunks)ios/Classes/FlutterInappPurchasePlugin.swift(10 hunks)ios/flutter_inapp_purchase.podspec(1 hunks)lib/enums.dart(0 hunks)lib/errors.dart(2 hunks)lib/flutter_inapp_purchase.dart(18 hunks)lib/modules/ios.dart(2 hunks)lib/types.dart(5 hunks)test/flutter_inapp_purchase_test.dart(1 hunks)test/ios_methods_test.dart(1 hunks)test/subscription_flow_test.dart(1 hunks)
💤 Files with no reviewable changes (1)
- lib/enums.dart
🧰 Additional context used
📓 Path-based instructions (9)
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
test/subscription_flow_test.dartexample/lib/src/screens/available_purchases_screen.darttest/ios_methods_test.darttest/flutter_inapp_purchase_test.dartlib/modules/ios.dartlib/errors.dartlib/types.dartlib/flutter_inapp_purchase.dart
test/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
test/**/*.dart: Add tests for new functionality or bug fixes
Ensure new code is covered by tests
For new features, add comprehensive tests
For bug fixes, write a failing test that demonstrates the bug
Files:
test/subscription_flow_test.darttest/ios_methods_test.darttest/flutter_inapp_purchase_test.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
test/subscription_flow_test.dartexample/lib/src/screens/available_purchases_screen.darttest/ios_methods_test.darttest/flutter_inapp_purchase_test.dartandroid/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktlib/modules/ios.dartlib/errors.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/types.dartlib/flutter_inapp_purchase.dart
example/**
📄 CodeRabbit inference engine (GEMINI.md)
Update the example app when needed for new features
Files:
example/lib/src/screens/available_purchases_screen.dartexample/ios/Flutter/Flutter.podspecexample/ios/Runner.xcodeproj/project.pbxprojexample/ios/Flutter/ephemeral/flutter_lldbinitexample/ios/Flutter/ephemeral/flutter_lldb_helper.pyexample/ios/Podfile
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Avoid using emojis in documentation, especially in headings
Files:
README.mddocs/docs/api/error-codes.md
{README.md,docs/**}
📄 CodeRabbit inference engine (GEMINI.md)
{README.md,docs/**}: Update relevant documentation when API changes are made
Document new features in README or API docs
Files:
README.mddocs/docs/api/error-codes.md
{android/**,ios/**}
📄 CodeRabbit inference engine (GEMINI.md)
For new features, implement for both iOS and Android when applicable
Files:
ios/flutter_inapp_purchase.podspecandroid/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktios/Classes/FlutterInappPurchasePlugin.swift
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.ktlib/modules/ios.dartlib/errors.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/types.dartlib/flutter_inapp_purchase.dart
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/modules/ios.dartlib/errors.dartlib/types.dartlib/flutter_inapp_purchase.dart
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: When adding a new feature, verify it aligns with the OpenIAP specification
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
test/subscription_flow_test.darttest/flutter_inapp_purchase_test.dartlib/types.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
test/ios_methods_test.dartlib/modules/ios.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to test/**/*.dart : Add tests for new functionality or bug fixes
Applied to files:
test/ios_methods_test.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: All changes must pass `flutter test`
Applied to files:
test/ios_methods_test.darttest/flutter_inapp_purchase_test.dart.githooks/pre-commit
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
test/flutter_inapp_purchase_test.dartdocs/docs/api/error-codes.mdios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
test/flutter_inapp_purchase_test.dartdocs/docs/api/error-codes.mdios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use RequestProductsParams; it has been removed
Applied to files:
test/flutter_inapp_purchase_test.dartlib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Applied to files:
test/flutter_inapp_purchase_test.dartdocs/docs/api/error-codes.mdios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Use --set-exit-if-changed with dart format to match CI behavior
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {README.md,docs/**} : Document new features in README or API docs
Applied to files:
README.md
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
Applied to files:
lib/types.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Applied to files:
lib/flutter_inapp_purchase.dart
🪛 GitHub Actions: Flutter CI
test/ios_methods_test.dart
[error] 1-1: Dart format changed formatting in this file. Run 'dart format' to apply formatting.
lib/flutter_inapp_purchase.dart
[error] 1-1: Dart format changed formatting in this file. Run 'dart format' to apply formatting.
🔇 Additional comments (32)
example/ios/Runner.xcodeproj/project.pbxproj (1)
289-290: Verify if manual OpenIAP.framework entries are needed.The Pods frameworks script usually handles embedding. Duplicating inputs/outputs can cause stale/duplicate-embed issues. Please confirm with a clean build or remove if redundant.
Also applies to: 293-295
ios/flutter_inapp_purchase.podspec (1)
17-19: OpenIAP dependency addition looks correct.Ensure CocoaPods resolves 1.1.7 from trunk; example Podfile pins via git.
example/ios/Podfile (1)
1-2: Add trunk CDN—LGTM.android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt (1)
557-559: Correct key read for replacement mode—LGTM.Fallback to legacy key preserves backward compatibility.
example/ios/Flutter/ephemeral/flutter_lldbinit (1)
5-5: LGTM: imports the helper relative to the init fileThis keeps the helper load stable across build paths.
lib/types.dart (3)
48-51: Const ctor for PurchaseOptions is goodReduces allocations at callsites.
340-349: OK: Android-only field usageParsing only
subscriptionOfferDetailsAndroidmatches the migration away from legacy fields.If any tests still send
subscriptionOfferDetails, update fixtures tosubscriptionOfferDetailsAndroid.
820-829: OK: Same alignment in Subscription.fromJsonConsistent with Product.fromJson.
test/flutter_inapp_purchase_test.dart (1)
181-185: iOS requestProducts uses fetchProducts with skus/type — LGTMMatches OpenIAP: passing { skus, type } where type is ProductType.subs. Good.
test/subscription_flow_test.dart (1)
83-98: Updated iOS mock to fetchProducts — LGTMSwitching to 'fetchProducts' with { skus, type } aligns with the iOS path in this PR.
lib/modules/ios.dart (1)
111-124: Channel method renames to IOS-suffixed endpoints — LGTMpresentCodeRedemptionSheetIOS/showManageSubscriptionsIOS correctly target iOS-specific entrypoints and enforce platform checks.
example/lib/src/screens/available_purchases_screen.dart (2)
112-116: Correct: filter active items on iOS via PurchaseOptions — LGTMUsing onlyIncludeActiveItemsIOS for available purchases matches the new iOS behavior.
123-132: History source selection per platform — LGTMiOS via getAvailablePurchases(...false) and Android via getPurchaseHistories() is consistent with the migration notes.
test/ios_methods_test.dart (1)
1-101: Run dart format to fix pipeline failureCI reports formatting changes. Please run: dart format .
#!/bin/bash # Format Dart files and show diff dart format . git --no-pager diff --exit-code || truelib/errors.dart (2)
85-101: Robust error normalization — LGTM_normalizeToErrorCode sensibly handles PurchaseError, String/Map codes, and legacy ints via ErrorCodeUtils. Good cross-platform coverage.
235-257: String/legacy code mapping in fromPlatformCode — LGTMAccepts OpenIAP string codes across platforms and falls back to iOS numeric mapping; safe default to eUnknown.
lib/flutter_inapp_purchase.dart (8)
10-10: LGTM! Good practice using import alias.Using the alias
iap_errfor the errors import helps avoid naming conflicts and makes the code more readable.
208-212: Good alignment with OpenIAP specification.The iOS implementation now correctly uses
fetchProductswith explicit type parameters, properly following the OpenIAP specification. This aligns with the learnings about using the correct OpenIAP terminology.
631-632: Fix method name inconsistency with OpenIAP.The native iOS method is called
getStorefrontIOS, but this should be consistent throughout the codebase as verified by the learnings.
672-672: Good consistency in iOS method naming.All iOS-specific methods now consistently use the
IOSsuffix as per the coding guidelines:presentCodeRedemptionSheetIOS,showManageSubscriptionsIOS,getPendingTransactionsIOS, etc. This follows the established naming convention.Also applies to: 698-698, 1404-1404, 2005-2005, 2017-2017
744-796: Excellent data normalization for iOS fields.The consistent use of
toString()for all string fields ensures type safety and prevents runtime errors from mixed types. This is particularly important for fields likeproductId,price,currencythat Dart expects as strings.
1214-1223: Smart error code handling with OpenIAP string codes.The implementation correctly prioritizes OpenIAP string codes over legacy numeric codes, providing better cross-platform compatibility. The fallback mechanism ensures backward compatibility.
1993-1995: Verify the OpenIAP restore flow implementation.The restore flow performs an end/init cycle which might cause state loss or event listener disruption. This approach seems unusual for a restore operation.
#!/bin/bash # Check if OpenIAP module has a dedicated restore method ast-grep --pattern 'restorePurchases' rg -n "restorePurchases|restoreTransactions" --type swiftConsider documenting why this approach was chosen or implementing a dedicated restore method if available in OpenIAP.
1354-1364: Remove deprecated parameter from method signature.The
prorationModeparameter is marked as deprecated butreplacementModeAndroidis already available. The deprecated parameter should be removed to avoid confusion.Future<dynamic> requestSubscription( String productId, { int? replacementModeAndroid, String? obfuscatedAccountIdAndroid, String? obfuscatedProfileIdAndroid, String? purchaseTokenAndroid, int? offerTokenIndex, }) async {Likely an incorrect or invalid review comment.
ios/Classes/FlutterInappPurchasePlugin.swift (8)
3-4: LGTM! Clean transition to OpenIAP.The import changes clearly show the migration from StoreKit to OpenIAP, with appropriate comments explaining the architectural decision.
26-27: Good practice: Early listener setup.Setting up OpenIAP listeners during plugin registration (Expo-style) ensures events aren't missed during initialization. This is a solid architectural decision.
136-137: LGTM! Proper no-op implementation.Changed from returning FlutterMethodNotImplemented to returning nil, preventing unnecessary exceptions on the Dart side as suggested in previous reviews.
228-235: LGTM! Event name and payload format fixed.The promoted product event now correctly uses
iap-promoted-productwith a string payload, matching what the Dart side expects. This fixes the issue identified in previous reviews.
472-472: LGTM! Return format fixed for getStorefrontIOS.Now correctly returns a dictionary with
countryCodekey, matching what the Dart side expects when it readsresult['countryCode'].
191-208: Good type coercion for Dart compatibility.The explicit string conversion for iOS-specific fields ensures Dart receives the expected types, preventing runtime type errors. This is particularly important for fields like
webOrderLineItemIdIOSandtransactionId.
516-523: Excellent dual-field support for OpenIAP compatibility.Providing both
jwsRepresentationandpurchaseToken(with the same value) ensures compatibility with both the OpenIAP specification and legacy code. This is a thoughtful approach to migration.
442-459: Verify promoted product serialization consistency.The method fetches the promoted product and then re-fetches it to get full serialization. This seems inefficient and might return different data if the product changes between calls.
#!/bin/bash # Check if OpenIAP provides complete product data in getPromotedProductIOS ast-grep --pattern 'getPromotedProductIOS' rg -A 10 "getPromotedProductIOS.*OpenIapProduct" --type swiftConsider caching the promoted product data or ensuring
getPromotedProductIOSreturns complete product information.
Run tests in pre-commit only when RUN_PRECOMMIT_TESTS=1 is set. Keeps hooks fast while still enabling teams to opt into test runs locally. README updated with instructions.
- Pre-commit: run flutter analyze (non-blocking by default, block when ENFORCE_ANALYZE=1).\n- README: document analyzer and test env toggles.\n- .gitignore: ignore example/ios/Flutter/ephemeral.\n- Dart: use explicit IapPlatform.ios/android in error paths per review.
Use iap_err.getCurrentPlatform() in receipt validation error paths to report the actual runtime platform, as discussed in review. Keeps success path platform explicit for clarity.
Run dart format across the repo so that the CI step 'dart format --set-exit-if-changed .' no longer detects diffs. This addresses the formatting failure caused by test/ios_methods_test.dart changes.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
.githooks/pre-commit (1)
21-27: Make analyzer blocking by default.Don’t mask analyzer failures; fail the commit unless explicitly opted out.
Apply:
echo "[pre-commit] Running analyzer..." -if [ "${ENFORCE_ANALYZE:-0}" = "1" ]; then - flutter analyze -else - # Do not fail commit on analyzer warnings by default - flutter analyze || true -fi +flutter analyze
🧹 Nitpick comments (2)
.githooks/pre-commit (1)
9-14: Handle filenames with spaces/newlines in staged formatting.Use NUL-delimited paths to avoid word-splitting issues.
Apply:
-# Collect staged Dart files and format them in place -STAGED_DART_FILES=$(git diff --cached --name-only --diff-filter=ACMRT | grep -E '\.dart$' || true) -if [ -n "$STAGED_DART_FILES" ]; then - dart format $STAGED_DART_FILES >/dev/null - # Re-stage formatted files - echo "$STAGED_DART_FILES" | xargs git add -A -- -fi +# Collect staged Dart files (NUL-delimited) and format them in place +mapfile -d '' STAGED_DART_FILES < <(git diff --cached --name-only -z --diff-filter=ACMRT | grep -z -E '\.dart$' || true) +if [ ${#STAGED_DART_FILES[@]} -gt 0 ]; then + printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 dart format >/dev/null + # Re-stage formatted files + printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 git add -A -- +fi @@ -echo "[pre-commit] Verifying format on staged Dart files..." -if [ -n "$STAGED_DART_FILES" ]; then - dart format --output=none --set-exit-if-changed $STAGED_DART_FILES -fi +echo "[pre-commit] Verifying format on staged Dart files..." +if [ ${#STAGED_DART_FILES[@]} -gt 0 ]; then + printf '%s\0' "${STAGED_DART_FILES[@]}" | xargs -0 dart format --output=none --set-exit-if-changed +fiAlso applies to: 16-19
lib/flutter_inapp_purchase.dart (1)
129-134: Unify platform reporting in errors/results.For consistency and accurate diagnostics, prefer
iap_err.getCurrentPlatform()over conditional_platform.isIOS ? ... : ...wherever settingplatformin PurchaseError/ReceiptValidationResult.Apply pattern:
- platform: _platform.isIOS - ? iap_types.IapPlatform.ios - : iap_types.IapPlatform.android, + platform: iap_err.getCurrentPlatform(),Note: Keep explicit platform only where the API is strictly single-platform and you intentionally want a fixed value.
Also applies to: 149-154, 175-180, 191-197, 254-260, 271-276, 285-289, 321-327, 383-387, 507-512, 548-553, 572-577, 609-614, 637-643, 649-654, 675-680, 701-706, 1267-1270, 1961-1967, 1736-1741, 1845-1846, 1851-1852
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.githooks/pre-commit(1 hunks).gitignore(1 hunks)README.md(1 hunks)lib/flutter_inapp_purchase.dart(17 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- README.md
🧰 Additional context used
📓 Path-based instructions (4)
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
lib/flutter_inapp_purchase.dart
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
lib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
lib/flutter_inapp_purchase.dart
🧠 Learnings (21)
📓 Common learnings
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Follow the OpenIAP specification for implementations
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Applied to files:
.gitignorelib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
Applied to files:
.gitignore
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code should pass `flutter analyze`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code must be formatted with `dart format`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Applied to files:
lib/flutter_inapp_purchase.dart
🔇 Additional comments (3)
.gitignore (1)
41-45: Good ignore additions for generated artifacts.Ignoring
*.cxx/andexample/ios/Flutter/ephemeral/prevents noisy diffs from build/LLDB helper files. Looks good.lib/flutter_inapp_purchase.dart (2)
208-212: Correct iOS fetch: using fetchProducts with {skus, type}.Matches OpenIAP and the project’s “no params object” guideline for requestProducts. LGTM.
2037-2046: API shape for promoted product aligned (Map-returning).Switch to
Future<Map<String, dynamic>?>resolves the earlier type mismatch risk. Back-compat for string payload is a nice touch.
On iOS, perform syncIOS and ignore sync errors, then fetch available purchases. On Android, fetch available purchases directly. Matches desired behavior to always refresh state while soft-failing sync, and consolidates logic into a cross-platform implementation.
- Remove getAppTransactionTyped and clearTransactionCache (not in OpenIAP spec).\n- Replace getProductsAsync with fetchProducts wrapper that calls requestProducts.\n- Remove presentCodeRedemptionSheet and showManageSubscriptions; use iOS-suffixed methods from iOS mixin.\n- Add clearTransactionIOS and getPromotedProductIOS to iOS mixin.
- Remove getAppTransactionTyped, clearTransactionCache, presentCodeRedemptionSheet, showManageSubscriptions, getProductsAsync.\n- Add fetchProducts wrapper, clearTransactionIOS, getPromotedProductIOS.\n- Update examples to use presentCodeRedemptionSheetIOS and showManageSubscriptionsIOS.
Expose requestPurchaseOnPromotedProductIOS in the iOS mixin to mirror native support and align with the documented API surface.
- Pre-commit now runs flutter test by default (skip with SKIP_PRECOMMIT_TESTS=1; set PRECOMMIT_TEST_CONCURRENCY to tune).\n- Fix ios_methods_test by removing const FakePlatform and returning proper getStorefrontIOS map shape.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/flutter_inapp_purchase.dart (1)
538-541: Fix iOS getAvailablePurchases double-encoding bug.Passing json.encode(result) to extractPurchases will break when native already returns a JSON string (common on iOS). Pass the raw result so extractPurchases can handle String or List.
Apply:
- dynamic result = await _channel.invokeMethod('getAvailableItems', args); - final items = extractPurchases(json.encode(result)) ?? []; + final dynamic result = + await _channel.invokeMethod('getAvailableItems', args); + final items = extractPurchases(result) ?? []; return items;
♻️ Duplicate comments (3)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)
9-19: Good defensive register/memory handlingUsing FindRegister(), validating x0/x1, bounding write size, and checking WriteMemory results address prior concerns. Thanks for tightening this up.
Also applies to: 21-33
lib/flutter_inapp_purchase.dart (2)
1215-1224: Optional: prefer runtime platform for error mapping.Consider using iap_err.getCurrentPlatform() instead of _platform.isIOS ternaries to keep diagnostics fully runtime-accurate (maintainer preference).
Apply:
- final detected = iap_err.ErrorCodeUtils.fromPlatformCode( - result.code!, - _platform.isIOS ? IapPlatform.ios : IapPlatform.android, - ); + final detected = iap_err.ErrorCodeUtils.fromPlatformCode( + result.code!, + iap_err.getCurrentPlatform(), + );
1962-1971: getPromotedProduct now returns Map — resolves prior mismatch.Backwards-compatible wrapping of String → {'productIdentifier': ...} is a good touch.
🧹 Nitpick comments (4)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (2)
35-43: Make breakpoint setup idempotent to avoid duplicates on re-importsIf this script is imported multiple times (e.g., LLDB restart, lldbinit reload), you’ll create duplicate breakpoints. Name the bp and skip creation when it already exists. Uses SBTarget.FindBreakpointsByName/SBBreakpointList. (lldb.llvm.org)
def __lldb_init_module(debugger: lldb.SBDebugger, _): - target = debugger.GetDummyTarget() + target = debugger.GetDummyTarget() + name = "openiap.rxpages" + try: + blist = lldb.SBBreakpointList(target) + if target.FindBreakpointsByName(name, blist) and blist.GetSize() > 0: + print("-- LLDB integration already loaded --") + return + except Exception: + # Best-effort: older LLDBs may lack SBBreakpointList/FindBreakpointsByName + pass @@ - bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + if hasattr(bp, "AddNameWithErrorHandling"): + err = bp.AddNameWithErrorHandling(name) # Newer LLDBs + if hasattr(err, "Success") and not err.Success(): + print(f"Warning: failed to name breakpoint '{name}': {err}") + elif hasattr(bp, "AddName"): + bp.AddName(name) # Fallback for older LLDBs bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) bp.SetAutoContinue(True) print("-- LLDB integration loaded --")
37-39: Tighten comment phrasingSuggest: “Use BreakpointCreateByRegex (not BreakpointCreateByName). The callback on the dummy target doesn’t carry over otherwise.”
- # Caveat: must use BreakpointCreateByRegEx here and not - # BreakpointCreateByName. For some reasons callback function does not - # get carried over from dummy target for the later. + # Caveat: use BreakpointCreateByRegex (not BreakpointCreateByName). + # The callback on the dummy target otherwise doesn't carry over to later targets.lib/modules/ios.dart (2)
115-118: New helper clearTransactionIOS().Looks fine for iOS-only no-op on other platforms. Optional: return a bool to indicate whether a clear was performed.
121-128: getPromotedProductIOS() return-shape handling is solid.Gracefully supports Map or String and normalizes to Map<String, dynamic>. Consider documenting expected keys (e.g., productIdentifier) to set API expectations for integrators.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py(1 hunks)example/lib/src/screens/debug_purchases_screen.dart(1 hunks)example/lib/src/screens/offer_code_screen.dart(1 hunks)example/lib/storekit2_demo.dart(2 hunks)lib/flutter_inapp_purchase.dart(16 hunks)lib/modules/ios.dart(2 hunks)test/ios_methods_test.dart(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- test/ios_methods_test.dart
🧰 Additional context used
📓 Path-based instructions (5)
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
example/lib/storekit2_demo.dartexample/lib/src/screens/debug_purchases_screen.dartexample/lib/src/screens/offer_code_screen.dartlib/modules/ios.dartlib/flutter_inapp_purchase.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
example/lib/storekit2_demo.dartexample/lib/src/screens/debug_purchases_screen.dartexample/lib/src/screens/offer_code_screen.dartlib/modules/ios.dartlib/flutter_inapp_purchase.dart
example/**
📄 CodeRabbit inference engine (GEMINI.md)
Update the example app when needed for new features
Files:
example/lib/storekit2_demo.dartexample/lib/src/screens/debug_purchases_screen.dartexample/lib/src/screens/offer_code_screen.dartexample/ios/Flutter/ephemeral/flutter_lldb_helper.py
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/modules/ios.dartlib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
lib/modules/ios.dartlib/flutter_inapp_purchase.dart
🧠 Learnings (13)
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.
Applied to files:
example/lib/storekit2_demo.dartexample/lib/src/screens/debug_purchases_screen.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
example/lib/storekit2_demo.dartlib/modules/ios.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Applied to files:
example/lib/storekit2_demo.dartexample/lib/src/screens/debug_purchases_screen.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: According to the OpenIAP specification at https://www.openiap.dev/docs/types#request-types, the parameter name for requesting products should be "skus" rather than "productIds" to maintain compliance with the OpenIAP standard.
Applied to files:
lib/flutter_inapp_purchase.dart
🪛 Ruff (0.12.2)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py
7-7: Unused function argument: bp_loc
(ARG001)
7-7: Unused function argument: extra_args
(ARG001)
7-7: Unused function argument: intern_dict
(ARG001)
🔇 Additional comments (17)
example/ios/Flutter/ephemeral/flutter_lldb_helper.py (1)
1-4: Confirm committing “ephemeral/” is intentionalThis path is usually regenerated by Flutter (flutter clean can wipe it). If you intend to keep this script, consider moving it to a stable path and importing it from lldbinit, or add safeguards in tooling.
example/lib/src/screens/offer_code_screen.dart (1)
73-73: iOS-specific API rename looks good.presentCodeRedemptionSheetIOS() aligns with the new IOS suffix guideline and the platform guard above. No further changes needed.
example/lib/src/screens/debug_purchases_screen.dart (1)
136-136: Correct iOS method usage.showManageSubscriptionsIOS() matches the IOS suffix convention and existing iOS guard. Fallback flow is appropriate.
example/lib/storekit2_demo.dart (1)
205-206: Consistent migration to IOS APIs.Both presentCodeRedemptionSheetIOS() and showManageSubscriptionsIOS() are correctly adopted with platform gating. Also good to see requestProducts using direct parameters (skus, type).
Also applies to: 225-226
lib/modules/ios.dart (2)
111-112: Channel method rename is correct.presentCodeRedemptionSheetIOS channel route matches the Dart API.
139-139: showManageSubscriptionsIOS channel invocation aligned.Route name matches Dart exposure; platform guard above is correct.
lib/flutter_inapp_purchase.dart (11)
10-10: Namespaced errors import is good.Using errors.dart as iap_err avoids type/name collisions with types.dart.
166-167: endConnection wrapper calls channel directly — OK.Method keeps state flag updates within the wrapper. No action needed.
206-209: iOS requestProducts path correctly uses fetchProducts arguments.Complies with "direct parameters (skus, type)" guideline.
629-630: Updated iOS storefront channel route.getStorefrontIOS path matches native side and iOS-only contract.
668-670: presentCodeRedemptionSheetIOS channel call aligned.Good adherence to IOS suffix and error handling wrapper.
694-696: showManageSubscriptionsIOS channel call aligned.Matches example usage and platform contract.
739-816: Product JSON normalization LGTM.Consistent toString() normalization and IOS/Android field mapping reduce runtime type issues across platforms.
Also applies to: 819-855, 858-881
1333-1360: Android subscription replacement validation is correct.DeveloperError when replacement mode set without purchaseTokenAndroid prevents bad upgrades/downgrades.
1631-1631: Good: use runtime platform in validation paths.Using iap_err.getCurrentPlatform() in ReceiptValidationResult aligns with maintainer guidance.
Also applies to: 1637-1637, 1782-1782, 1788-1788, 1794-1794, 1801-1801, 1808-1808, 1814-1814
1895-1901: fetchProducts wrapper is fine.Thin alias to requestProducts with direct parameters meets the simplified API design.
1930-1951: restorePurchases flow improvement LGTM.Lightweight iOS sync via syncIOS() with soft-fail and then getAvailablePurchases() is a sensible cross-platform behavior.
…nsactionCache - Handle 'requestPurchase' with OpenIAP props; remove 'buyProduct' case and its helper.\n- Remove 'clearTransactionCache' method case from iOS plugin.\n- Update Dart calls to invoke 'requestPurchase' on iOS.\n- Update tests to expect 'requestPurchase' and mock it accordingly.
- Update iOS invocation to 'requestPurchase' for purchaseAsync.\n- Update tests to mock/expect 'requestPurchase' instead of 'buyProduct'.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (4)
.githooks/pre-commit (1)
21-27: Nice: analyzer step added per earlier reviewThanks for adding the analyzer to the hook; this addresses the prior suggestion.
lib/flutter_inapp_purchase.dart (3)
1962-1973: Address type mismatch in getPromotedProduct.Based on the past review comments, this method still has a type mismatch issue. The Dart method signature declares
Future<Map<String, dynamic>?>but existing code may expect a string product identifier.Consider providing a migration path or ensuring backward compatibility for code that expects a string result.
1215-1224: Fix unresolved identifier in error handling.The code references
IapPlatformwithout proper namespace qualification, which will cause a compilation error.Apply this fix:
- _platform.isIOS ? IapPlatform.ios : IapPlatform.android, + iap_err.getCurrentPlatform(),This also aligns with the maintainer's preference for using runtime platform detection.
1373-1373: Fix method name to follow iOS naming convention.The method call should use the iOS-specific naming pattern with the
IOSsuffix.Apply this fix:
- dynamic result = await _channel.invokeMethod('getPendingTransactionsIOS'); + final dynamic result = await _channel.invokeMethod('getPendingTransactionsIOS');The method name is already correct, but improve the variable declaration style for consistency.
🧹 Nitpick comments (13)
.githooks/pre-commit (5)
4-6: Gate pub get to avoid unnecessary network workRunning flutter pub get on every commit can slow local loops. Make it opt-in or conditional.
-echo "[pre-commit] Running flutter pub get..." -flutter pub get >/dev/null +if [ "${SKIP_PUB_GET:-0}" != "1" ]; then + echo "[pre-commit] Running flutter pub get..." + flutter pub get >/dev/null +fi
7-14: Handle filenames with spaces/newlines when formatting and re-stagingCurrent approach splits on whitespace; switch to NUL-delimited files from git to be robust.
echo "[pre-commit] Auto-formatting staged Dart files..." -# Collect staged Dart files and format them in place -STAGED_DART_FILES=$(git diff --cached --name-only --diff-filter=ACMRT | grep -E '\.dart$' || true) -if [ -n "$STAGED_DART_FILES" ]; then - dart format $STAGED_DART_FILES >/dev/null - # Re-stage formatted files - echo "$STAGED_DART_FILES" | xargs git add -A -- -fi +# Format staged Dart files robustly (handles spaces/newlines) +while IFS= read -r -d '' file; do + case "$file" in + *.dart) + dart format "$file" >/dev/null + git add -- "$file" + ;; + esac +done < <(git diff --cached --name-only -z --diff-filter=ACMRT)
16-19: Align with team learning: verify repo-wide formatting once before commitPrior learnings emphasize running dart format --set-exit-if-changed . as a final check. Also removes reliance on the earlier STAGED_DART_FILES var.
-echo "[pre-commit] Verifying format on staged Dart files..." -if [ -n "$STAGED_DART_FILES" ]; then - dart format --output=none --set-exit-if-changed $STAGED_DART_FILES -fi +echo "[pre-commit] Verifying format..." +if [ "${VERIFY_ALL_FORMAT:-1}" = "1" ]; then + dart format --output=none --set-exit-if-changed . +else + # Verify only staged Dart files when full verification is disabled + while IFS= read -r -d '' file; do + case "$file" in + *.dart) dart format --output=none --set-exit-if-changed "$file" ;; + esac + done < <(git diff --cached --name-only -z --diff-filter=ACMRT) +fi
21-27: Enforce analyzer by default and skip implicit pub to speed runsMatches retrieved learnings (“Run flutter analyze and ensure it passes before committing.”) and avoids redundant pub.
echo "[pre-commit] Running analyzer..." -if [ "${ENFORCE_ANALYZE:-0}" = "1" ]; then - flutter analyze +if [ "${ENFORCE_ANALYZE:-1}" = "1" ]; then + flutter analyze --no-pub else # Do not fail commit on analyzer warnings by default - flutter analyze || true + flutter analyze --no-pub || true fiWould you like me to also switch to dart analyze with --fatal-warnings/--fatal-infos for stricter gating?
29-32: Tweak tests step: remove “fast fail” wording and avoid implicit pubflutter test doesn’t support a fail-fast flag; add --no-pub to save time since pub get already ran.
if [ "${SKIP_PRECOMMIT_TESTS:-0}" != "1" ]; then - echo "[pre-commit] Running tests (fast fail)..." - flutter test --concurrency="${PRECOMMIT_TEST_CONCURRENCY:-4}" + echo "[pre-commit] Running tests..." + flutter test --no-pub --concurrency="${PRECOMMIT_TEST_CONCURRENCY:-4}" fiios/Classes/FlutterInappPurchasePlugin.swift (8)
43-46: canMakePayments always returns true — consider delegating to OpenIAP.If OpenIAP exposes a capability check, prefer returning its result to avoid false positives on restricted devices.
- // OpenIAP abstraction: assume payments can be made once initialized - result(true) + // Prefer real capability if available + if let can = try? await OpenIapModule.shared.canMakePayments() { + result(can) + } else { + result(true) + }
156-164: Return a truthy value for initConnection to preserve prior API shape.Dart often expects a boolean. Returning nil can propagate as null.
- _ = try await OpenIapModule.shared.initConnection() - result(nil) + _ = try await OpenIapModule.shared.initConnection() + result(true)
171-174: Same for endConnection: return true on success.Keeps parity with Android path and common expectations.
- _ = try? await OpenIapModule.shared.endConnection() - result(nil) + _ = try? await OpenIapModule.shared.endConnection() + result(true)
226-234: Remove unused variable in promotedProduct listener.‘payload’ is computed but not used.
- let payload: [String: Any] = ["productId": productId] - // Emit event that Dart expects: name 'iap-promoted-product' with String payload - self.channel?.invokeMethod("iap-promoted-product", arguments: productId) + // Emit event that Dart expects: name 'iap-promoted-product' with String payload + self.channel?.invokeMethod("iap-promoted-product", arguments: productId)
362-371: SwiftLint: redundant optional initialization.Initialize optionals without “= nil”.
- var withOffer: OpenIapDiscountOffer? = nil + var withOffer: OpenIapDiscountOffer?
525-533: Unify error shape in clearTransactionIOS.For consistency with other methods, use standardized error codes/messages and move the underlying error into details.
- await MainActor.run { - result(FlutterError(code: OpenIapError.E_SERVICE_ERROR, message: error.localizedDescription, details: nil)) - } + await MainActor.run { + let code = OpenIapError.E_SERVICE_ERROR + result(FlutterError(code: code, message: defaultMessage(for: code), details: error.localizedDescription)) + }
176-183: Dead code cleanup: updateListenerTask appears unused.If no longer needed, remove the property and related cleanup.
- private var updateListenerTask: Task<Void, Never>? ... - updateListenerTask?.cancel() - updateListenerTask = nil
300-346: Minor: nested MainActor dispatch is redundant.Methods are already @mainactor; the extra MainActor.run blocks can be dropped.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
.githooks/pre-commit(1 hunks)ios/Classes/FlutterInappPurchasePlugin.swift(8 hunks)lib/flutter_inapp_purchase.dart(18 hunks)lib/modules/ios.dart(2 hunks)test/ios_methods_test.dart(1 hunks)test/purchase_flow_test.dart(1 hunks)test/subscription_flow_test.dart(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/modules/ios.dart
🧰 Additional context used
📓 Path-based instructions (6)
**/*.dart
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.dart: Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Do not use RequestProductsParams; it has been removed
When using generic functions (e.g., showModalBottomSheet), always specify explicit type arguments (e.g., showModalBottomSheet)
Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
When iOS is not the final suffix, use Ios (e.g., IosManager, IosHelper)
For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Use Android as the suffix for Android-related types and functions (e.g., PurchaseAndroid)
When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
When combined with platform suffixes, place Id before the suffix (e.g., subscriptionGroupIdIOS, obfuscatedAccountIdAndroid)
Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Do not use generic prefixes like get or find for API methods; follow OpenIAP terminology
**/*.dart: Code must be formatted withdart format
Code should passflutter analyze
Files:
test/purchase_flow_test.darttest/ios_methods_test.darttest/subscription_flow_test.dartlib/flutter_inapp_purchase.dart
test/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
test/**/*.dart: Add tests for new functionality or bug fixes
Ensure new code is covered by tests
For new features, add comprehensive tests
For bug fixes, write a failing test that demonstrates the bug
Files:
test/purchase_flow_test.darttest/ios_methods_test.darttest/subscription_flow_test.dart
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
{**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}: Add appropriate error handling for edge cases
Add comments only for complex logic that isn't self-explanatory
Files:
test/purchase_flow_test.darttest/ios_methods_test.darttest/subscription_flow_test.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
{lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}}
📄 CodeRabbit inference engine (GEMINI.md)
Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
{android/**,ios/**}
📄 CodeRabbit inference engine (GEMINI.md)
For new features, implement for both iOS and Android when applicable
Files:
ios/Classes/FlutterInappPurchasePlugin.swift
lib/**/*.dart
📄 CodeRabbit inference engine (GEMINI.md)
Follow the simplified API design where methods use direct parameters instead of parameter objects
Files:
lib/flutter_inapp_purchase.dart
🧠 Learnings (25)
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: All changes must pass `flutter test`
Applied to files:
test/purchase_flow_test.darttest/ios_methods_test.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Functions that depend on event results should use the request prefix (e.g., requestPurchase, requestSubscription)
Applied to files:
test/purchase_flow_test.darttest/subscription_flow_test.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use IOS as the suffix for iOS-related types and functions (e.g., PurchaseIOS)
Applied to files:
test/ios_methods_test.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to test/**/*.dart : Add tests for new functionality or bug fixes
Applied to files:
test/ios_methods_test.dart
📚 Learning: 2025-09-09T06:04:46.721Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#545
File: lib/flutter_inapp_purchase.dart:1665-1665
Timestamp: 2025-09-09T06:04:46.721Z
Learning: In flutter_inapp_purchase project, prefer using iap_err.getCurrentPlatform() in error handling over hardcoded platform constants to ensure accurate runtime platform reporting in error messages and debugging.
Applied to files:
test/ios_methods_test.dartios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Update calls to requestProducts to use direct parameters (skus, optional type) instead of a params object
Applied to files:
test/subscription_flow_test.dartlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Before PR, run: `dart format --set-exit-if-changed .`, `flutter analyze`, `flutter test`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Re-run dart format --set-exit-if-changed . as a final verification before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run dart format --set-exit-if-changed . and ensure it passes before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter analyze and fix all lints before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Run flutter test and ensure all tests pass before committing
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code should pass `flutter analyze`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to **/*.dart : Code must be formatted with `dart format`
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Only commit if all pre-commit checks succeed with exit code 0
Applied to files:
.githooks/pre-commit
📚 Learning: 2025-08-21T02:01:17.634Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/faq.md:164-172
Timestamp: 2025-08-21T02:01:17.634Z
Learning: When reviewing PRs for flutter_inapp_purchase, focus on the current version being developed rather than versioned documentation files, as those are historical snapshots.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T02:25:46.402Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/index.md:65-69
Timestamp: 2025-08-21T02:25:46.402Z
Learning: When reviewing flutter_inapp_purchase documentation, `purchaseUpdatedListener` and `purchaseErrorListener` are the correct listener names in the public API, not `purchaseUpdated` and `purchaseError`.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Exception: In iOS-only contexts, standalone fields ending with ID use ID (e.g., transactionID, webOrderLineItemID)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : For fields with iOS in the middle, use Id before IOS (e.g., subscriptionGroupIdIOS)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : When IAP is not the final suffix, use Iap (e.g., IapPurchase, not IAPPurchase)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:58:54.628Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-31T19:58:54.628Z
Learning: Applies to **/*.dart : Use Id consistently across identifiers (e.g., productId, transactionId, offerId)
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swift
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification for parameter naming. According to the specification at https://www.openiap.dev/docs/types#request-types, the parameter name should be "skus" (not "productIds") when requesting products, as confirmed by the project maintainer hyochan.
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: The flutter_inapp_purchase library follows the OpenIAP specification at https://www.openiap.dev/docs/types#request-types for parameter naming conventions. According to hyochan, "skus" is the correct parameter name based on this specification, not "productIds".
Applied to files:
ios/Classes/FlutterInappPurchasePlugin.swiftlib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {lib/**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Use consistent platform suffixes in names (IOS for iOS, Android for Android)
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-31T19:59:33.928Z
Learnt from: CR
PR: hyochan/flutter_inapp_purchase#0
File: GEMINI.md:0-0
Timestamp: 2025-08-31T19:59:33.928Z
Learning: Applies to {**/*.dart,android/**/*.{kt,kts,java},ios/**/*.{swift,m,mm,h}} : Add appropriate error handling for edge cases
Applied to files:
lib/flutter_inapp_purchase.dart
📚 Learning: 2025-08-21T01:20:28.525Z
Learnt from: hyochan
PR: hyochan/flutter_inapp_purchase#531
File: docs/versioned_docs/version-6.3/api/overview.md:128-131
Timestamp: 2025-08-21T01:20:28.525Z
Learning: According to the OpenIAP specification at https://www.openiap.dev/docs/types#request-types, the parameter name for requesting products should be "skus" rather than "productIds" to maintain compliance with the OpenIAP standard.
Applied to files:
lib/flutter_inapp_purchase.dart
🪛 SwiftLint (0.57.0)
ios/Classes/FlutterInappPurchasePlugin.swift
[Warning] 362-362: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (26)
.githooks/pre-commit (1)
1-3: Good: strict Bash mode in a shebanged hookset -euo pipefail is appropriate for a CI-like pre-commit workflow.
test/purchase_flow_test.dart (1)
303-304: iOS rename to requestPurchase is correct.Matches the new API and guideline to use the request prefix for event-driven calls.
test/subscription_flow_test.dart (3)
49-51: Mock handler updated to requestPurchase.Aligned with OpenIAP rename; keeps tests consistent with the plugin surface.
83-97: iOS fetchProducts mock aligns with new contract.Using { skus, type } and filtering by skus matches the updated API and naming spec (“skus”).
332-334: Assertion updated to requestPurchase.Good: the iOS path now expects requestPurchase and passes sku.
test/ios_methods_test.dart (4)
68-77: IOS-specific method tests look good.presentCodeRedemptionSheetIOS and showManageSubscriptionsIOS are invoked and asserted correctly.
79-83: getStorefrontIOS expectation matches wrapper behavior.Native returns { countryCode }, Dart wrapper returns the String — test is correct.
92-98: getPendingTransactionsIOS test is solid.Mocks a native list and asserts typed Purchase fields; channel method name matches.
85-90: KeepgetPromotedProductas-is. It’s the public, platform-agnostic API dispatching internally togetPromotedProductIOS, which aligns with our naming guidelines.ios/Classes/FlutterInappPurchasePlugin.swift (1)
496-500: Storefront response shape matches Dart wrapper expectations.Good fix to return { countryCode } map.
lib/flutter_inapp_purchase.dart (16)
10-10: LGTM! Namespaced import aligns with error handling preferences.The change to namespace the import as
iap_errfollows the established pattern and supports the maintainer's preference for usingiap_err.getCurrentPlatform()in error handling.
166-167: LGTM! Consistent channel method invocation.The unified
endConnectionmethod call removes platform-specific duplication while maintaining the same functionality.
205-209: LGTM! iOS method properly uses fetchProducts with type parameter.The iOS implementation correctly calls
fetchProductswith explicit type parameter, aligning with OpenIAP conventions per the coding guidelines.
308-311: LGTM! iOS purchase request properly includes appAccountToken parameter.The method call correctly includes the
appAccountTokenparameter for iOS purchase requests, maintaining OpenIAP compliance.
629-629: LGTM! Method name follows iOS naming convention.The method name
getStorefrontIOScorrectly follows the coding guidelines for iOS-specific methods using theIOSsuffix.
669-669: LGTM! Method name follows iOS naming convention.The method call
presentCodeRedemptionSheetIOScorrectly follows the coding guidelines for iOS-specific methods using theIOSsuffix.
695-695: LGTM! Method name follows iOS naming convention.The method call
showManageSubscriptionsIOScorrectly follows the coding guidelines for iOS-specific methods using theIOSsuffix.
741-863: LGTM! String normalization ensures consistent data types.The extensive use of
.toString()for various fields ensures consistent string types across iOS and Android platforms, preventing type-related issues in Dart.
1333-1333: LGTM! Simplified variable assignment.The direct assignment of
replacementModeAndroidtoeffectiveReplacementModeremoves unnecessary intermediate processing while maintaining the same functionality.
1361-1361: LGTM! iOS subscription request uses correct method.The method call
requestPurchasefor iOS subscriptions follows the OpenIAP pattern and coding guidelines.
1631-1631: LGTM! Error handling uses runtime platform detection.Based on the retrieved learnings, using
iap_err.getCurrentPlatform()in error handling is the preferred approach as it provides accurate runtime platform information for debugging and error reporting.Also applies to: 1637-1637, 1781-1781, 1787-1787, 1793-1793, 1800-1800, 1807-1807, 1813-1813
1895-1901: LGTM! Generic fetchProducts method aligns with OpenIAP.The new
fetchProductsmethod with generic typing follows OpenIAP conventions and properly delegates to the existingrequestProductsimplementation.
1926-1926: LGTM! Documentation clarifies API changes.The comment properly documents the removal of
finishTransactionAsyncin favor of the more consistentfinishTransaction(Purchase)method.
1930-1952: LGTM! Cross-platform restore logic is well-implemented.The
restorePurchasesmethod provides consistent behavior across platforms with appropriate error handling. iOS uses sync with soft-fail behavior, while both platforms fetch available purchases.
1954-1960: LGTM! Documentation clarifies API migration.The comments clearly document the removal of deprecated methods and guide users to the appropriate iOS-specific replacements.
1975-1977: LGTM! Documentation clarifies API changes.The comments properly document the removal of deprecated App Transaction methods in favor of iOS-specific alternatives.
iOS Migration to OpenIAP Module
iOS Integration
fetchProducts,*IOSmethodsEvents
Example
getAvailablePurchasesCleanup
Summary by CodeRabbit
New Features
Refactor
Documentation
Tests
Chores