Migration from v5.x to v7.0+
This guide helps you migrate from flutter_inapp_purchase v5.x to the latest v7.0+ version.
Overview
Version 7.0+ is a major update with significant breaking changes to align with the OpenIAP specification and support modern platform APIs.
If you're migrating from v5.x, we recommend:
- First migrate to v6.8 following this guide
- Then migrate from v6.8 to v7.0 using the v6 to v7 migration guide
Installation
Update your pubspec.yaml
:
dependencies:
flutter_inapp_purchase: ^7.0.0
Key Changes Summary
- ✅ OpenIAP Compliance - Full alignment with OpenIAP specification
- ✅ StoreKit 2 Support for iOS 15.0+
- ✅ Billing Client v8 for Android
- ✅ Improved Type Safety with refined APIs
- ✅ Better Error Handling with standardized error codes
- ⚠️ Breaking Changes in error codes, method signatures, and data models
Major Breaking Changes
1. Error Code Format (CRITICAL)
The most significant breaking change is the error code enum format.
v5.x (Old):
// SCREAMING_SNAKE_CASE with E_ prefix
ErrorCode.E_USER_CANCELLED
ErrorCode.E_NETWORK_ERROR
ErrorCode.E_ITEM_UNAVAILABLE
ErrorCode.E_ALREADY_OWNED
ErrorCode.E_DEVELOPER_ERROR
v6.8+ (New):
// PascalCase format (OpenIAP standard)
ErrorCode.UserCancelled
ErrorCode.NetworkError
ErrorCode.ItemUnavailable
ErrorCode.AlreadyOwned
ErrorCode.DeveloperError
Migration:
// Before
if (error.code == ErrorCode.E_USER_CANCELLED) { ... }
// After
if (error.code == ErrorCode.UserCancelled) { ... }
2. Data Models
v5.x:
IAPItem
for products- String-based product IDs
- Nullable fields everywhere
v6.8+:
Product
/ProductCommon
for products- Strongly typed with required fields
- Better null safety
// Before (v5.x)
class IAPItem {
String? productId;
String? price;
String? currency;
String? localizedPrice;
}
// After (v6.8+)
class Product {
String id; // Required
double? price; // Numeric
String currency; // Required
String displayPrice; // Required (localized)
ProductType type; // Required
}
3. fetchProducts API
v5.x:
// Old approach
List<IAPItem> items = await FlutterInappPurchase.instance
.fetchProducts(skus: _productIds, type: 'inapp');
v6.8+:
// New OpenIAP-compliant approach
final result = await FlutterInappPurchase.instance.fetchProducts(
ProductRequest(
skus: _productIds,
type: ProductQueryType.InApp,
),
);
final products = result.value;
4. requestPurchase API
v5.x:
// Simple string-based request
String msg = await FlutterInappPurchase.instance
.requestPurchase(item.productId);
v6.8+:
// Structured request with platform-specific options
final requestProps = RequestPurchaseProps.inApp(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(
sku: productId,
quantity: 1,
),
android: RequestPurchaseAndroidProps(
skus: [productId],
),
),
);
await FlutterInappPurchase.instance.requestPurchase(requestProps);
5. finishTransaction API
v5.x:
// iOS only, simple call
await FlutterInappPurchase.instance.finishTransaction(item);
v6.8+:
// Cross-platform with explicit consumable flag
await FlutterInappPurchase.instance.finishTransaction(
purchase: item,
isConsumable: true, // Required parameter
);
6. Stream Access
v5.x:
// Direct static stream access
FlutterInappPurchase.iapUpdated.listen(...);
v6.8+:
// Instance-based stream access
FlutterInappPurchase.instance.purchaseUpdated.listen(...);
FlutterInappPurchase.instance.purchaseError.listen(...);
7. Android Methods
v5.x:
await FlutterInappPurchase.instance.consumePurchase(
purchaseToken: token,
);
await FlutterInappPurchase.instance.acknowledgePurchase(
purchaseToken: token,
);
v6.8+:
// Explicit Android suffix for clarity
await FlutterInappPurchase.instance.consumePurchaseAndroid(
purchaseToken: token,
);
await FlutterInappPurchase.instance.acknowledgePurchaseAndroid(
purchaseToken: token,
);
Step-by-Step Migration
Step 1: Update Dependencies
Update your pubspec.yaml
:
dependencies:
flutter_inapp_purchase: ^6.8.0
Run:
flutter pub get
Step 2: Update Error Code Handling
Find and replace all error code references:
Search for:
ErrorCode.E_USER_CANCELLED
ErrorCode.E_NETWORK_ERROR
ErrorCode.E_ITEM_UNAVAILABLE
ErrorCode.E_ALREADY_OWNED
Replace with:
ErrorCode.UserCancelled
ErrorCode.NetworkError
ErrorCode.ItemUnavailable
ErrorCode.AlreadyOwned
Step 3: Update Stream Listeners
Before:
_purchaseUpdatedSubscription = FlutterInappPurchase.iapUpdated.listen((data) {
print('purchase-updated: $data');
});
_purchaseErrorSubscription = FlutterInappPurchase.iapUpdated.listen((data) {
print('purchase-error: $data');
});
After:
_purchaseUpdatedSubscription = FlutterInappPurchase.instance
.purchaseUpdated.listen((purchase) {
if (purchase != null) {
print('purchase-updated: ${purchase.productId}');
_handlePurchaseUpdate(purchase);
}
});
_purchaseErrorSubscription = FlutterInappPurchase.instance
.purchaseError.listen((error) {
print('purchase-error: ${error?.message}');
_handlePurchaseError(error);
});
Step 4: Update Product Fetching
Before:
Future<void> _getProducts() async {
List<IAPItem> items = await FlutterInappPurchase.instance
.fetchProducts(skus: _productIds, type: 'inapp');
setState(() {
_items = items;
});
}
After:
Future<void> _getProducts() async {
final result = await FlutterInappPurchase.instance.fetchProducts(
ProductRequest(
skus: _productIds,
type: ProductQueryType.InApp,
),
);
setState(() {
_items = result.value;
});
}
Step 5: Update Purchase Requests
Before:
void _requestPurchase(IAPItem item) {
FlutterInappPurchase.instance.requestPurchase(item.productId);
}
After:
Future<void> _requestPurchase(ProductCommon item) async {
final requestProps = RequestPurchaseProps.inApp(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(
sku: item.id,
quantity: 1,
),
android: RequestPurchaseAndroidProps(
skus: [item.id],
),
),
);
await FlutterInappPurchase.instance.requestPurchase(requestProps);
}
Step 6: Update Transaction Completion
Before:
// iOS
await FlutterInappPurchase.instance.finishTransaction(item);
// Android
await FlutterInappPurchase.instance.consumePurchase(
purchaseToken: item.purchaseToken!,
);
After:
// iOS
await FlutterInappPurchase.instance.finishTransaction(
purchase: item,
isConsumable: true,
);
// Android
await FlutterInappPurchase.instance.consumePurchaseAndroid(
purchaseToken: item.purchaseToken!,
);
Step 7: Update Field Access
Before:
final id = product.productId;
final price = product.localizedPrice;
After:
final id = product.id;
final price = product.displayPrice;
Complete Migration Example
Before (v5.x)
class _MyStoreState extends State<MyStore> {
StreamSubscription? _purchaseSubscription;
List<IAPItem> _items = [];
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
await FlutterInappPurchase.instance.initConnection();
_purchaseSubscription = FlutterInappPurchase.iapUpdated.listen((data) {
print('purchase: $data');
_finishTransaction(data);
});
final items = await FlutterInappPurchase.instance
.fetchProducts(skus: _productIds, type: 'inapp');
setState(() {
_items = items;
});
}
void _buy(IAPItem item) {
FlutterInappPurchase.instance.requestPurchase(item.productId);
}
void _finishTransaction(Purchase item) {
FlutterInappPurchase.instance.finishTransaction(item);
}
}
After (v6.8+)
class _MyStoreState extends State<MyStore> {
StreamSubscription? _purchaseSubscription;
List<ProductCommon> _items = [];
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
await FlutterInappPurchase.instance.initConnection();
_purchaseSubscription = FlutterInappPurchase.instance
.purchaseUpdated.listen((purchase) {
if (purchase != null) {
print('purchase: ${purchase.productId}');
_finishTransaction(purchase);
}
});
final result = await FlutterInappPurchase.instance.fetchProducts(
ProductRequest(
skus: _productIds,
type: ProductQueryType.InApp,
),
);
setState(() {
_items = result.value;
});
}
Future<void> _buy(ProductCommon item) async {
final requestProps = RequestPurchaseProps.inApp(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(
sku: item.id,
quantity: 1,
),
android: RequestPurchaseAndroidProps(
skus: [item.id],
),
),
);
await FlutterInappPurchase.instance.requestPurchase(requestProps);
}
Future<void> _finishTransaction(Purchase item) async {
await FlutterInappPurchase.instance.finishTransaction(
purchase: item,
isConsumable: true,
);
}
void dispose() {
_purchaseSubscription?.cancel();
super.dispose();
}
}
Testing Checklist
After migration, test the following:
- App compiles without errors
- IAP connection initializes successfully
- Products load correctly
- Purchase flow completes
- Transactions finish properly
- Error handling works (test by cancelling a purchase)
- Restore purchases works
- Both iOS and Android platforms tested
Common Migration Issues
Issue 1: "ErrorCode.E_USER_CANCELLED not found"
Solution: Update to new PascalCase format:
ErrorCode.UserCancelled
Issue 2: "productId doesn't exist"
Solution: Change to id
:
// Before
item.productId
// After
item.id
Issue 3: "localizedPrice doesn't exist"
Solution: Change to displayPrice
:
// Before
item.localizedPrice
// After
item.displayPrice
Issue 4: "type argument required"
Solution: Add ProductQueryType:
ProductRequest(
skus: ['product_id'],
type: ProductQueryType.InApp, // Required
)
Benefits of v6.8+
- OpenIAP Standard: Full compliance with OpenIAP specification
- Better Type Safety: Strongly typed APIs with fewer runtime errors
- Improved Error Handling: Clearer error codes and messages
- Modern Platform Support: Latest iOS StoreKit 2 and Android Billing v8
- Better Documentation: Comprehensive guides aligned with OpenIAP
- Cross-Platform Consistency: Unified API across iOS and Android
Need Help?
If you encounter issues during migration:
- Check the Troubleshooting Guide
- Review the API Documentation
- Look at the Examples
- Open an issue on GitHub
- Join our Discord Community