Quick Start
Get up and running with Flutter In-App Purchase in minutes.
Complete Example
Here's a complete example implementing a simple store with products and subscriptions:
import 'package:flutter/material.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
import 'dart:async';
class SimpleStore extends StatefulWidget {
_SimpleStoreState createState() => _SimpleStoreState();
}
class _SimpleStoreState extends State<SimpleStore> {
StreamSubscription? _purchaseUpdatedSubscription;
StreamSubscription? _purchaseErrorSubscription;
List<IAPItem> _products = [];
List<IAPItem> _subscriptions = [];
List<PurchasedItem> _purchases = [];
// Your product IDs from App Store Connect / Google Play Console
final List<String> _productIds = [
'com.example.coins_100',
'com.example.coins_500',
];
final List<String> _subscriptionIds = [
'com.example.premium_monthly',
'com.example.premium_yearly',
];
void initState() {
super.initState();
initIAP();
}
void dispose() {
_purchaseUpdatedSubscription?.cancel();
_purchaseErrorSubscription?.cancel();
super.dispose();
}
// Initialize the plugin
Future<void> initIAP() async {
// Initialize connection
await FlutterInappPurchase.instance.initConnection();
print('IAP connection initialized');
// Set up purchase listeners
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen((productItem) {
print('Purchase updated: ${productItem?.productId}');
_handlePurchaseUpdate(productItem!);
});
_purchaseErrorSubscription =
FlutterInappPurchase.purchaseError.listen((purchaseError) {
print('Purchase error: $purchaseError');
_showError('Purchase failed: ${purchaseError.message}');
});
// Load products and purchases
await _getProducts();
await _getPurchases();
}
// Get available products
Future<void> _getProducts() async {
try {
// Get consumable products
List<IAPItem> products =
await FlutterInappPurchase.instance.requestProducts(
skus: _productIds,
type: 'inapp',
);
// Get subscriptions
List<IAPItem> subscriptions =
await FlutterInappPurchase.instance.requestProducts(
skus: _subscriptionIds,
type: 'subs',
);
setState(() {
_products = products;
_subscriptions = subscriptions;
});
} catch (e) {
_showError('Failed to load products: $e');
}
}
// Get previous purchases
Future<void> _getPurchases() async {
try {
List<PurchasedItem>? purchases =
await FlutterInappPurchase.instance.getAvailablePurchases();
setState(() {
_purchases = purchases ?? [];
});
} catch (e) {
_showError('Failed to load purchases: $e');
}
}
// Handle purchase updates
void _handlePurchaseUpdate(PurchasedItem productItem) async {
// Verify purchase on your server here
bool isValid = await _verifyPurchase(productItem);
if (isValid) {
// Deliver the product to user
await _deliverProduct(productItem);
// Finish the transaction
if (Platform.isIOS) {
await FlutterInappPurchase.instance.finishTransaction(productItem);
} else if (productItem.isConsumableAndroid ?? false) {
await FlutterInappPurchase.instance.consumePurchase(
purchaseToken: productItem.purchaseTokenAndroid!,
);
} else {
await FlutterInappPurchase.instance.acknowledgePurchase(
purchaseToken: productItem.purchaseTokenAndroid!,
);
}
// Update UI
await _getPurchases();
_showSuccess('Purchase successful!');
}
}
// Request a purchase
Future<void> _requestPurchase(String productId) async {
try {
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: productId),
android: RequestPurchaseAndroid(skus: [productId]),
),
type: PurchaseType.inapp,
);
} catch (e) {
_showError('Purchase failed: $e');
}
}
// Request a subscription
Future<void> _requestSubscription(String productId) async {
try {
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: productId),
android: RequestPurchaseAndroid(skus: [productId]),
),
type: PurchaseType.subs,
);
} catch (e) {
_showError('Subscription failed: $e');
}
}
// Restore purchases
Future<void> _restorePurchases() async {
try {
await FlutterInappPurchase.instance.getAvailablePurchases();
_showSuccess('Purchases restored!');
} catch (e) {
_showError('Restore failed: $e');
}
}
// Verify purchase (implement your server logic)
Future<bool> _verifyPurchase(PurchasedItem item) async {
// TODO: Verify receipt with your server
// For now, just return true
return true;
}
// Deliver product (implement your logic)
Future<void> _deliverProduct(PurchasedItem item) async {
// TODO: Deliver the product to user
print('Delivering product: ${item.productId}');
}
// UI Helper methods
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), backgroundColor: Colors.red),
);
}
void _showSuccess(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), backgroundColor: Colors.green),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('In-App Purchase Example'),
actions: [
IconButton(
icon: Icon(Icons.restore),
onPressed: _restorePurchases,
tooltip: 'Restore Purchases',
),
],
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Products Section
Text('Products', style: Theme.of(context).textTheme.headline6),
SizedBox(height: 8),
..._products.map((product) => Card(
child: ListTile(
title: Text(product.title ?? product.productId ?? ''),
subtitle: Text(product.description ?? ''),
trailing: TextButton(
child: Text(product.localizedPrice ?? ''),
onPressed: () => _requestPurchase(product.productId!),
),
),
)),
SizedBox(height: 24),
// Subscriptions Section
Text('Subscriptions', style: Theme.of(context).textTheme.headline6),
SizedBox(height: 8),
..._subscriptions.map((subscription) => Card(
child: ListTile(
title: Text(subscription.title ?? subscription.productId ?? ''),
subtitle: Text(subscription.description ?? ''),
trailing: TextButton(
child: Text(subscription.localizedPrice ?? ''),
onPressed: () => _requestSubscription(subscription.productId!),
),
leading: _isPurchased(subscription.productId!)
? Icon(Icons.check_circle, color: Colors.green)
: null,
),
)),
SizedBox(height: 24),
// Active Purchases Section
Text('Active Purchases', style: Theme.of(context).textTheme.headline6),
SizedBox(height: 8),
if (_purchases.isEmpty)
Text('No active purchases'),
..._purchases.map((purchase) => Card(
child: ListTile(
title: Text(purchase.productId ?? 'Unknown'),
subtitle: Text('Purchased: ${DateTime.fromMillisecondsSinceEpoch(
purchase.transactionDate ?? 0
)}'),
),
)),
],
),
),
);
}
bool _isPurchased(String productId) {
return _purchases.any((purchase) => purchase.productId == productId);
}
}
Key Concepts
1. Initialization
Always initialize the connection before using any other methods:
await FlutterInappPurchase.instance.initConnection();
2. Loading Products
Fetch products using their IDs:
// Regular products
List<IAPItem> products = await FlutterInappPurchase.instance
.requestProducts(skus: ['product_id_1', 'product_id_2'], type: 'inapp');
// Subscriptions
List<IAPItem> subscriptions = await FlutterInappPurchase.instance
.requestProducts(skus: ['subscription_id_1', 'subscription_id_2'], type: 'subs');
3. Purchase Flow
Listen to purchase updates and handle them appropriately:
// Listen to successful purchases
FlutterInappPurchase.purchaseUpdated.listen((productItem) {
// 1. Verify purchase
// 2. Deliver content
// 3. Finish transaction
});
// Listen to purchase errors
FlutterInappPurchase.purchaseError.listen((error) {
// Handle error
});
// Request a purchase
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: 'product_id'),
android: RequestPurchaseAndroid(skus: ['product_id']),
),
type: PurchaseType.inapp, // or PurchaseType.subs for subscriptions
);
4. Platform Differences
Handle platform-specific requirements:
if (Platform.isIOS) {
// iOS: Always finish transactions
await FlutterInappPurchase.instance.finishTransaction(item);
} else {
// Android: Acknowledge or consume
if (isConsumable) {
await FlutterInappPurchase.instance.consumePurchase(
purchaseToken: item.purchaseTokenAndroid!,
);
} else {
await FlutterInappPurchase.instance.acknowledgePurchase(
purchaseToken: item.purchaseTokenAndroid!,
);
}
}
Best Practices
- Always verify purchases server-side before delivering content
- Handle all error cases to provide good user experience
- Test thoroughly with sandbox/test accounts
- Restore purchases when users reinstall or switch devices
- Clean up listeners in dispose() to prevent memory leaks
Next Steps
- Products Guide - Working with consumable products
- Subscriptions Guide - Implementing subscriptions
- Receipt Validation - Secure purchase verification
- Error Handling - Handling edge cases