🔄 Migration from v5.x to v6.0
This guide helps you migrate your existing flutter_inapp_purchase v5.x implementation to the new 6.0.0 release with StoreKit 2 and Billing Client v8 support.
🎯 Overview
Version 6.0.0 is a major release with breaking changes to support modern platform APIs and improve developer experience.
Key Changes Summary
- ✅ StoreKit 2 Support for iOS 15.0+
- ✅ Billing Client v8 for Android
- ✅ Improved Error Handling with better error codes
- ✅ Enhanced Type Safety with refined APIs
- ⚠️ Breaking Changes in error codes and some method signatures
💥 Breaking Changes
1. Error Code Enum Changes (CRITICAL)
The most significant breaking change is the error code enum format.
v5.x (Old):
// SCREAMING_SNAKE_CASE format
ErrorCode.E_USER_CANCELLED
ErrorCode.E_NETWORK_ERROR
ErrorCode.E_ITEM_UNAVAILABLE
ErrorCode.E_ALREADY_OWNED
ErrorCode.E_DEVELOPER_ERROR
v6.0 (New):
// lowerCamelCase format
ErrorCode.eUserCancelled
ErrorCode.eNetworkError
ErrorCode.eItemUnavailable
ErrorCode.eAlreadyOwned
ErrorCode.eDeveloperError
Before (v5.x):
Future<List<IAPItem>> getProducts(List<String> skus) async
Future<String> requestPurchase(String sku) async
After (v6.x):
Future<List<IAPItem>> getProducts(List<String> skus) async
Future<void> requestPurchase(String sku) async
2. Method Return Types
Several methods now return void
instead of String
:
// Old
String result = await FlutterInappPurchase.instance.requestPurchase(sku);
// New
await FlutterInappPurchase.instance.requestPurchase(sku);
// Result comes through purchaseUpdated stream
3. Stream Names
Purchase streams have been renamed for clarity:
Before:
FlutterInappPurchase.connectionUpdated.listen(...);
FlutterInappPurchase.iapUpdated.listen(...);
After:
FlutterInappPurchase.purchaseUpdated.listen(...);
FlutterInappPurchase.purchaseError.listen(...);
Step-by-Step Migration
Step 1: Update Dependencies
Update your pubspec.yaml
:
dependencies:
flutter_inapp_purchase: ^6.0.0 # Updated version
Run:
flutter pub get
Step 2: Enable Null Safety
If you haven't already, migrate your project to null safety:
dart migrate
Step 3: Update Initialization
Before:
Future<void> initPlatformState() async {
try {
await FlutterInappPurchase.instance.initConnection();
print('IAP connection initialized');
} on PlatformException catch (e) {
print('Failed to initialize connection: $e');
}
}
After:
Future<void> initPlatformState() async {
try {
await FlutterInappPurchase.instance.initConnection();
print('IAP connection initialized');
} catch (e) {
print('Failed to initialize IAP: $e');
}
}
Step 4: 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.purchaseUpdated.listen((productItem) {
if (productItem != null) {
print('purchase-updated: ${productItem.productId}');
_handlePurchaseUpdate(productItem);
}
});
_purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((productItem) {
print('purchase-error: ${productItem?.productId}');
_handlePurchaseError(productItem);
});
Step 5: Update Purchase Methods
Before:
try {
String msg = await FlutterInappPurchase.instance.requestPurchase(item.productId);
print('requestPurchase: $msg');
} catch (error) {
print('requestPurchase error: $error');
}
After:
try {
await FlutterInappPurchase.instance.requestPurchase(item.productId!);
// Success/failure will be delivered via streams
} catch (error) {
print('requestPurchase error: $error');
}
Step 6: Update Data Model Access
Before:
// Accessing properties without null checks
String productId = item.productId;
String price = item.localizedPrice;
After:
// Null-safe property access
String? productId = item.productId;
String? price = item.localizedPrice;
// With null coalescing
String displayPrice = item.localizedPrice ?? 'N/A';
Step 7: Update Transaction Completion
Before:
try {
String msg = await FlutterInappPurchase.instance.finishTransaction(item);
} catch (err) {
print('finishTransaction error: $err');
}
After:
try {
String? result = await FlutterInappPurchase.instance.finishTransaction(item);
print('Transaction finished: $result');
} catch (err) {
print('finishTransaction error: $err');
}
Complete Migration Example
Here's a complete before/after example:
Before (v5.x)
class _MyAppState extends State<MyApp> {
StreamSubscription _purchaseUpdatedSubscription;
StreamSubscription _purchaseErrorSubscription;
List<IAPItem> _items = [];
List<PurchasedItem> _purchases = [];
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
await FlutterInappPurchase.instance.initConnection();
_purchaseUpdatedSubscription = FlutterInappPurchase.iapUpdated.listen((data) {
print('purchase-updated: $data');
setState(() {
_purchases.add(data);
});
});
_purchaseErrorSubscription = FlutterInappPurchase.iapUpdated.listen((data) {
print('purchase-error: $data');
});
_getProduct();
}
void _requestPurchase(IAPItem item) {
FlutterInappPurchase.instance.requestPurchase(item.productId);
}
Future _getProduct() async {
List<IAPItem> items = await FlutterInappPurchase.instance.requestProducts(skus: _kProductIds, type: 'inapp');
setState(() {
_items = items;
});
}
}
After (v6.x)
class _MyAppState extends State<MyApp> {
StreamSubscription? _purchaseUpdatedSubscription;
StreamSubscription? _purchaseErrorSubscription;
List<IAPItem> _items = [];
List<PurchasedItem> _purchases = [];
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
try {
await FlutterInappPurchase.instance.initConnection();
print('IAP connection initialized');
} catch (e) {
print('Failed to initialize: $e');
}
_purchaseUpdatedSubscription = FlutterInappPurchase
.purchaseUpdated.listen((productItem) {
if (productItem != null) {
print('purchase-updated: ${productItem.productId}');
setState(() {
_purchases.add(productItem);
});
// Finish the transaction
_finishTransaction(productItem);
}
});
_purchaseErrorSubscription = FlutterInappPurchase
.purchaseError.listen((productItem) {
print('purchase-error: ${productItem?.productId}');
// Handle error
});
_getProduct();
}
void _requestPurchase(IAPItem item) async {
try {
await FlutterInappPurchase.instance.requestPurchase(item.productId!);
} catch (e) {
print('Purchase request failed: $e');
}
}
Future<void> _finishTransaction(PurchasedItem item) async {
try {
await FlutterInappPurchase.instance.finishTransaction(item);
} catch (e) {
print('Failed to finish transaction: $e');
}
}
Future<void> _getProduct() async {
try {
List<IAPItem> items = await FlutterInappPurchase.instance
.requestProducts(skus: _kProductIds, type: 'inapp');
setState(() {
_items = items;
});
} catch (e) {
print('Failed to get products: $e');
}
}
void dispose() {
_purchaseUpdatedSubscription?.cancel();
_purchaseErrorSubscription?.cancel();
super.dispose();
}
}
Testing Your Migration
- Compile Check: Ensure your code compiles without errors
- Initialize Test: Verify IAP initialization works
- Product Loading: Test product fetching
- Purchase Flow: Test complete purchase flow
- Stream Handling: Verify purchase update streams work
- Transaction Completion: Test finishing transactions
Benefits of v6.x
- Null Safety: Better type safety and fewer runtime errors
- Improved Error Handling: More precise error information
- Better Stream API: Clearer separation of success and error streams
- Updated Dependencies: Latest Android and iOS billing libraries
- Performance Improvements: Optimized native code
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