Event Listeners
Real-time event streams for monitoring purchase transactions, connection states, and other IAP events in flutter_inapp_purchase v6.0.0.
Core Event Streams
purchaseUpdated
Stream for successful purchase completions.
static Stream<PurchasedItem?> get purchaseUpdated
Type: Stream<PurchasedItem?>
Emits: Purchase completion events
Null Safety: Can emit null values - always check for null
Example:
StreamSubscription<PurchasedItem?>? _purchaseSubscription;
void setupPurchaseListener() {
_purchaseSubscription = FlutterInappPurchase.purchaseUpdated.listen(
(purchase) {
if (purchase != null) {
handlePurchaseSuccess(purchase);
}
},
onError: (error) {
print('Purchase stream error: $error');
},
);
}
Future<void> handlePurchaseSuccess(PurchasedItem purchase) async {
print('Purchase completed: ${purchase.productId}');
try {
// 1. Verify the purchase (recommended)
final isValid = await verifyPurchaseOnServer(purchase);
if (!isValid) {
print('Purchase verification failed');
return;
}
// 2. Deliver the product to user
await deliverProduct(purchase.productId);
// 3. Finish the transaction
await FlutterInappPurchase.instance.finishTransaction(
purchase,
isConsumable: true, // Set appropriately for your product
);
print('Purchase processed successfully');
} catch (e) {
print('Error processing purchase: $e');
}
}
void dispose() {
_purchaseSubscription?.cancel();
}
Platform Data:
- iOS: Contains
transactionReceipt
,originalTransactionIdentifierIOS
- Android: Contains
purchaseToken
,dataAndroid
,signatureAndroid
purchaseError
Stream for purchase failures and errors.
static Stream<PurchaseResult?> get purchaseError
Type: Stream<PurchaseResult?>
Emits: Purchase error events
Null Safety: Can emit null values - always check for null
Example:
StreamSubscription<PurchaseResult?>? _errorSubscription;
void setupErrorListener() {
_errorSubscription = FlutterInappPurchase.purchaseError.listen(
(error) {
if (error != null) {
handlePurchaseError(error);
}
},
);
}
void handlePurchaseError(PurchaseResult error) {
print('Purchase failed: ${error.message}');
print('Error code: ${error.responseCode}');
print('Debug info: ${error.debugMessage}');
switch (error.responseCode) {
case 1: // User cancelled
// Don't show error for user cancellation
print('User cancelled the purchase');
break;
case 2: // Network error
showUserMessage('Network error. Please check your connection and try again.');
break;
case 7: // Already owned
showUserMessage('You already own this item.');
// Optionally trigger restore purchases
restorePreviousPurchases();
break;
default:
showUserMessage('Purchase failed: ${error.message ?? 'Unknown error'}');
}
}
Common Error Codes:
1
- User cancelled2
- Network error3
- Service unavailable4
- Item unavailable7
- Already owned8
- Invalid purchase
connectionUpdated
Stream for store connection state changes.
static Stream<ConnectionResult> get connectionUpdated
Type: Stream<ConnectionResult>
Emits: Connection state changes
Never Null: Always emits valid ConnectionResult
objects
Example:
StreamSubscription<ConnectionResult>? _connectionSubscription;
void setupConnectionListener() {
_connectionSubscription = FlutterInappPurchase.connectionUpdated.listen(
(connectionResult) {
handleConnectionChange(connectionResult);
},
);
}
void handleConnectionChange(ConnectionResult result) {
if (result.connected) {
print('Store connected: ${result.message ?? 'Successfully connected'}');
// Connection established - safe to load products
loadProducts();
} else {
print('Store disconnected: ${result.message ?? 'Connection lost'}');
// Handle disconnection - disable purchase UI
disablePurchaseButtons();
// Optionally attempt reconnection
scheduleReconnection();
}
}
Future<void> scheduleReconnection() async {
await Future.delayed(Duration(seconds: 5));
try {
await FlutterInappPurchase.instance.initConnection();
} catch (e) {
print('Reconnection failed: $e');
}
}
purchasePromoted
Stream for promoted purchase events (iOS App Store promotions).
static Stream<String?> get purchasePromoted
Type: Stream<String?>
Emits: Product ID of promoted purchases
Platform: iOS only
Example:
StreamSubscription<String?>? _promotedSubscription;
void setupPromotedListener() {
_promotedSubscription = FlutterInappPurchase.purchasePromoted.listen(
(productId) {
if (productId != null) {
handlePromotedPurchase(productId);
}
},
);
}
Future<void> handlePromotedPurchase(String productId) async {
print('Promoted purchase initiated for: $productId');
try {
// Load product information
final products = await FlutterInappPurchase.instance.getProducts([productId]);
if (products.isEmpty) {
print('Promoted product not found: $productId');
return;
}
final product = products.first;
// Show promotional purchase UI
final shouldPurchase = await showPromotedPurchaseDialog(product);
if (shouldPurchase) {
// Proceed with purchase
final request = RequestPurchase(
ios: RequestPurchaseIOS(sku: productId, quantity: 1),
android: RequestPurchaseAndroid(skus: [productId]),
);
await FlutterInappPurchase.instance.requestPurchase(
request: request,
type: PurchaseType.inapp,
);
}
} catch (e) {
print('Error handling promoted purchase: $e');
}
}
Requirements: iOS 11.0+, promoted purchases configured in App Store Connect
inAppMessageAndroid
Stream for Google Play in-app messaging events.
static Stream<int?> get inAppMessageAndroid
Type: Stream<int?>
Emits: Message type codes
Platform: Android only
Example:
StreamSubscription<int?>? _inAppMessageSubscription;
void setupInAppMessageListener() {
if (Platform.isAndroid) {
_inAppMessageSubscription = FlutterInappPurchase.inAppMessageAndroid.listen(
(messageType) {
if (messageType != null) {
handleInAppMessage(messageType);
}
},
);
}
}
void handleInAppMessage(int messageType) {
print('In-app message received: $messageType');
switch (messageType) {
case 0: // Purchase message
print('Purchase-related message shown');
break;
case 1: // Billing message
print('Billing-related message shown');
break;
case 2: // Price change message
print('Price change message shown');
break;
default:
print('Unknown message type: $messageType');
}
}
Message Types:
0
- Purchase messages1
- Billing messages2
- Price change notifications3
- Generic messages
Complete Listener Setup
Full Implementation Example
class IAPListenerManager {
StreamSubscription<PurchasedItem?>? _purchaseSubscription;
StreamSubscription<PurchaseResult?>? _errorSubscription;
StreamSubscription<ConnectionResult>? _connectionSubscription;
StreamSubscription<String?>? _promotedSubscription;
StreamSubscription<int?>? _inAppMessageSubscription;
bool _isListening = false;
void startListening() {
if (_isListening) return;
// Purchase success listener
_purchaseSubscription = FlutterInappPurchase.purchaseUpdated.listen(
(purchase) {
if (purchase != null) {
_handlePurchaseSuccess(purchase);
}
},
onError: (error) {
print('Purchase stream error: $error');
},
);
// Purchase error listener
_errorSubscription = FlutterInappPurchase.purchaseError.listen(
(error) {
if (error != null) {
_handlePurchaseError(error);
}
},
onError: (error) {
print('Error stream error: $error');
},
);
// Connection state listener
_connectionSubscription = FlutterInappPurchase.connectionUpdated.listen(
(connectionResult) {
_handleConnectionChange(connectionResult);
},
onError: (error) {
print('Connection stream error: $error');
},
);
// iOS promoted purchases
if (Platform.isIOS) {
_promotedSubscription = FlutterInappPurchase.purchasePromoted.listen(
(productId) {
if (productId != null) {
_handlePromotedPurchase(productId);
}
},
);
}
// Android in-app messages
if (Platform.isAndroid) {
_inAppMessageSubscription = FlutterInappPurchase.inAppMessageAndroid.listen(
(messageType) {
if (messageType != null) {
_handleInAppMessage(messageType);
}
},
);
}
_isListening = true;
print('IAP listeners started');
}
void stopListening() {
_purchaseSubscription?.cancel();
_errorSubscription?.cancel();
_connectionSubscription?.cancel();
_promotedSubscription?.cancel();
_inAppMessageSubscription?.cancel();
_purchaseSubscription = null;
_errorSubscription = null;
_connectionSubscription = null;
_promotedSubscription = null;
_inAppMessageSubscription = null;
_isListening = false;
print('IAP listeners stopped');
}
Future<void> _handlePurchaseSuccess(PurchasedItem purchase) async {
// Implementation from examples above
}
void _handlePurchaseError(PurchaseResult error) {
// Implementation from examples above
}
void _handleConnectionChange(ConnectionResult result) {
// Implementation from examples above
}
Future<void> _handlePromotedPurchase(String productId) async {
// Implementation from examples above
}
void _handleInAppMessage(int messageType) {
// Implementation from examples above
}
}
Widget Integration
class IAPWidget extends StatefulWidget {
_IAPWidgetState createState() => _IAPWidgetState();
}
class _IAPWidgetState extends State<IAPWidget> {
final IAPListenerManager _listenerManager = IAPListenerManager();
bool _isConnected = false;
void initState() {
super.initState();
_initializeIAP();
}
Future<void> _initializeIAP() async {
try {
// Start listening before initializing connection
_listenerManager.startListening();
// Initialize connection
await FlutterInappPurchase.instance.initConnection();
setState(() {
_isConnected = true;
});
} catch (e) {
print('IAP initialization failed: $e');
}
}
void dispose() {
_listenerManager.stopListening();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('In-App Purchases'),
),
body: _isConnected
? PurchaseContent()
: Center(
child: CircularProgressIndicator(),
),
);
}
}
Error Handling Best Practices
1. Null Safety
Always check for null values in stream emissions:
FlutterInappPurchase.purchaseUpdated.listen((purchase) {
if (purchase != null) {
// Safe to use purchase
processPurchase(purchase);
} else {
print('Received null purchase event');
}
});
2. Stream Error Handling
Handle stream errors to prevent app crashes:
FlutterInappPurchase.purchaseUpdated.listen(
(purchase) {
// Handle success
},
onError: (error) {
print('Purchase stream error: $error');
// Optionally restart the stream or show user message
},
onDone: () {
print('Purchase stream closed');
// Handle stream closure
},
);
3. Subscription Lifecycle
Properly manage subscription lifecycle:
class SubscriptionManager {
StreamSubscription? _subscription;
void start() {
_subscription ??= FlutterInappPurchase.purchaseUpdated.listen(
handlePurchase,
onError: handleError,
);
}
void stop() {
_subscription?.cancel();
_subscription = null;
}
void restart() {
stop();
start();
}
}
Platform Differences
iOS-Specific Considerations
- Transaction State: Use
transactionStateIOS
for detailed state - Receipt Data: Access via
transactionReceipt
- Original Transaction: Available via
originalTransactionIdentifierIOS
- Promoted Purchases: Only available on iOS 11.0+
Android-Specific Considerations
- Purchase State: Use
purchaseStateAndroid
for state information - Purchase Token: Essential for consumption and acknowledgment
- Pending Purchases: Handle state
2
for pending purchases - In-App Messages: Android-specific messaging system
Performance Optimization
1. Lazy Listener Setup
Only set up listeners when needed:
StreamSubscription? _purchaseSubscription;
void startPurchaseFlow() {
// Set up listener only when starting purchase
_purchaseSubscription ??= FlutterInappPurchase.purchaseUpdated.listen(
handlePurchase,
);
// Proceed with purchase
requestPurchase();
}
void completePurchaseFlow() {
// Clean up listener after purchase flow
_purchaseSubscription?.cancel();
_purchaseSubscription = null;
}
2. Debounced Error Handling
Avoid overwhelming users with repeated errors:
Timer? _errorDebounceTimer;
void handlePurchaseError(PurchaseResult error) {
_errorDebounceTimer?.cancel();
_errorDebounceTimer = Timer(Duration(seconds: 2), () {
showErrorToUser(error.message);
});
}
Troubleshooting
Common Issues
- Missing Purchases: Ensure listeners are set up before
initConnection()
- Memory Leaks: Always cancel subscriptions in
dispose()
- Null Emissions: Always check for null in stream handlers
- Platform Crashes: Handle stream errors with
onError
Debug Logging
void setupDebugLogging() {
FlutterInappPurchase.purchaseUpdated.listen(
(purchase) {
print('🛒 Purchase: ${purchase?.productId ?? 'null'}');
},
onError: (error) {
print('❌ Purchase Error: $error');
},
);
FlutterInappPurchase.purchaseError.listen(
(error) {
print('🚫 Error: ${error?.message ?? 'null'}');
},
);
FlutterInappPurchase.connectionUpdated.listen(
(result) {
print('🔗 Connection: ${result.connected ? 'Connected' : 'Disconnected'}');
},
);
}
See Also
- Core Methods - Methods that trigger these events
- Types - Event data structures
- Error Codes - Error handling reference
- Purchase Guide - Complete purchase flow implementation