Troubleshooting
Common issues and solutions when working with flutter_inapp_purchase v6.0.0.
Prerequisites Checklist
Before troubleshooting, ensure you have completed the basic setup:
Flutter Setup
- Flutter SDK 3.0.0 or higher
- Dart SDK 2.17.0 or higher
- flutter_inapp_purchase v6.0.0 added to
pubspec.yaml
- Run
flutter pub get
after adding dependency
Project Configuration
- Minimum SDK versions set correctly:
- Android:
minSdkVersion 21
or higher - iOS:
ios.deploymentTarget = '12.0'
or higher
- Android:
- Platform-specific permissions configured
- Bundle ID matches store configuration
# pubspec.yaml
dependencies:
flutter_inapp_purchase: ^6.0.0
# android/app/build.gradle
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
}
}
# ios/Runner.xcodeproj/project.pbxproj
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
App Store Setup (iOS)
Required Configurations
-
App Store Connect Setup
- App registered in App Store Connect
- Bundle ID matches your app
- In-App Purchases configured and approved
- Test users added to sandbox
-
Xcode Configuration
- In-App Purchase capability enabled
- Code signing configured
- Bundle ID matches App Store Connect
-
Product Configuration
- Product IDs match exactly (case-sensitive)
- Products are in "Ready to Submit" status
- At least one screenshot uploaded per product
// Verify your product IDs match exactly
final productIds = [
'com.yourapp.premium', // Must match App Store Connect
'com.yourapp.coins_100', // Case-sensitive
];
// Test with actual product IDs from App Store Connect
final products = await FlutterInappPurchase.instance.getProducts(productIds);
debugPrint('Found ${products.length} products');
Google Play Setup (Android)
Required Configurations
-
Google Play Console Setup
- App uploaded to Play Console (at least Internal Testing)
- In-app products created and activated
- License testing accounts configured
- App bundle signed and uploaded
-
Android Configuration
-
BILLING
permission in AndroidManifest.xml - Play Billing Library dependency (handled by plugin)
- ProGuard rules configured if using code obfuscation
-
-
Testing Setup
- License testing accounts added in Play Console
- Test using signed APK/AAB (not debug build)
- Products are "Active" in Play Console
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="com.android.vending.BILLING" />
// Test connection on Android
Future<void> testAndroidConnection() async {
try {
final result = await FlutterInappPurchase.instance.initConnection();
debugPrint('Android connection result: $result');
// Test product loading
final products = await FlutterInappPurchase.instance.getProducts([
'your_product_id_from_play_console'
]);
debugPrint('Loaded ${products.length} products');
} catch (e) {
debugPrint('Android connection failed: $e');
}
}
Common Issues
requestProducts() returns an empty array
Symptoms:
getProducts()
orrequestProducts()
returns empty list- Products configured in store but not loading
Solutions:
class ProductLoadingTroubleshooter {
static Future<void> diagnoseProductLoading() async {
final productIds = ['your.product.id'];
// 1. Check connection first
final connected = await _ensureConnection();
if (!connected) {
debugPrint('❌ Store not connected');
return;
}
// 2. Try loading products with error handling
try {
await FlutterInappPurchase.instance.requestProducts(
RequestProductsParams(skus: productIds, type: PurchaseType.inapp),
);
final products = await FlutterInappPurchase.instance.getProducts(productIds);
if (products.isEmpty) {
debugPrint('❌ No products loaded');
await _diagnoseEmptyProducts(productIds);
} else {
debugPrint('✅ Loaded ${products.length} products');
for (final product in products) {
debugPrint('Product: ${product.productId} - ${product.title}');
}
}
} catch (e) {
debugPrint('❌ Product loading error: $e');
}
}
static Future<void> _diagnoseEmptyProducts(List<String> productIds) async {
debugPrint('Diagnosing empty product list...');
// Check product ID format
for (final id in productIds) {
debugPrint('Checking product ID: $id');
if (Platform.isIOS) {
// iOS product IDs should not contain bundle ID
if (id.contains('.')) {
debugPrint('⚠️ iOS product ID contains dots - verify format');
}
} else if (Platform.isAndroid) {
// Android product IDs are typically reverse domain notation
if (!id.contains('.')) {
debugPrint('⚠️ Android product ID missing dots - verify format');
}
}
}
// Suggest solutions
debugPrint('\n🔧 Troubleshooting steps:');
debugPrint('1. Verify product IDs match store configuration exactly');
debugPrint('2. Check if products are approved/active in store');
debugPrint('3. Ensure app version matches store configuration');
debugPrint('4. Try with test product IDs first');
}
}
useIAP hook not working
Problem: Provider or state management not working properly
Solutions:
// Ensure proper provider setup
class IAPProviderSetup {
static Widget setupProvider(Widget child) {
return IapProviderWidget(
child: child,
);
}
}
// In your main.dart
void main() {
runApp(
IAPProviderSetup.setupProvider(
MyApp(),
),
);
}
// Access provider in widgets
class ProductScreen extends StatelessWidget {
Widget build(BuildContext context) {
final iapProvider = IapProvider.of(context);
if (iapProvider == null) {
return Text('❌ IAP Provider not found - check widget tree');
}
if (!iapProvider.connected) {
return Text('⏳ Connecting to store...');
}
return Text('✅ Store connected');
}
}
Purchase flow issues
Common purchase problems and solutions:
class PurchaseFlowTroubleshooter {
static void setupComprehensivePurchaseFlow() {
// 1. Set up listeners BEFORE making purchases
FlutterInappPurchase.purchaseUpdated.listen((purchase) {
if (purchase != null) {
debugPrint('✅ Purchase successful: ${purchase.productId}');
_handlePurchaseSuccess(purchase);
}
});
FlutterInappPurchase.purchaseError.listen((error) {
if (error != null) {
debugPrint('❌ Purchase error: ${error.message}');
_handlePurchaseError(error);
}
});
}
static Future<void> makePurchaseWithDiagnostics(String productId) async {
debugPrint('🛒 Initiating purchase for: $productId');
try {
// Pre-purchase checks
final connected = await _verifyConnection();
if (!connected) {
throw Exception('Store not connected');
}
final productExists = await _verifyProduct(productId);
if (!productExists) {
throw Exception('Product not found: $productId');
}
// Make purchase
await FlutterInappPurchase.instance.requestPurchase(
request: RequestPurchase(
ios: RequestPurchaseIOS(sku: productId, quantity: 1),
android: RequestPurchaseAndroid(skus: [productId]),
),
type: PurchaseType.inapp,
);
debugPrint('📱 Purchase dialog should appear now');
} catch (e) {
debugPrint('❌ Purchase initiation failed: $e');
_suggestPurchaseSolutions(e);
}
}
static void _handlePurchaseError(PurchaseResult error) {
switch (error.responseCode) {
case 1:
debugPrint('User cancelled purchase');
break;
case 7:
debugPrint('User already owns this item - consuming...');
_handleAlreadyOwned(error);
break;
default:
debugPrint('Purchase error ${error.responseCode}: ${error.message}');
}
}
}
Connection issues
Connection problems and diagnostics:
class ConnectionDiagnostics {
static Future<void> runConnectionDiagnostics() async {
debugPrint('🔍 Running connection diagnostics...');
// Test 1: Basic connection
try {
await FlutterInappPurchase.instance.initConnection();
debugPrint('✅ Basic connection successful');
} catch (e) {
debugPrint('❌ Basic connection failed: $e');
return;
}
// Test 2: Platform-specific checks
if (Platform.isIOS) {
await _checkIOSConnection();
} else if (Platform.isAndroid) {
await _checkAndroidConnection();
}
// Test 3: Product loading test
await _testProductLoading();
}
static Future<void> _checkIOSConnection() async {
debugPrint('🍎 Checking iOS connection...');
try {
// Check if payments are allowed
final canMakePayments = await FlutterInappPurchase.instance.initialize();
if (!canMakePayments) {
debugPrint('❌ Device cannot make payments');
debugPrint('💡 Check: Screen Time restrictions, parental controls');
return;
}
debugPrint('✅ iOS payments are allowed');
} catch (e) {
debugPrint('❌ iOS connection check failed: $e');
}
}
static Future<void> _checkAndroidConnection() async {
debugPrint('🤖 Checking Android connection...');
try {
final connectionState = await FlutterInappPurchase.instance.getConnectionStateAndroid();
debugPrint('Android connection state: $connectionState');
if (connectionState != 'connected') {
debugPrint('❌ Android billing service not connected');
debugPrint('💡 Check: Google Play Services, Play Store app updates');
return;
}
debugPrint('✅ Android billing service connected');
} catch (e) {
debugPrint('❌ Android connection check failed: $e');
}
}
}
Platform-specific issues
iOS Specific:
class IOSTroubleshooting {
static Future<void> diagnoseIOSIssues() async {
debugPrint('🍎 Diagnosing iOS-specific issues...');
// Check sandbox vs production
if (kDebugMode) {
debugPrint('Running in DEBUG mode - using iOS Sandbox');
debugPrint('💡 Ensure you have sandbox test account signed in');
} else {
debugPrint('Running in RELEASE mode - using Production');
}
// Check StoreKit availability
try {
final promoted = await FlutterInappPurchase.instance.getPromotedProduct();
debugPrint('StoreKit promotional products available: ${promoted != null}');
} catch (e) {
debugPrint('StoreKit check failed: $e');
}
// Common iOS issues
debugPrint('\n🔧 Common iOS solutions:');
debugPrint('1. Sign out and back into sandbox account in Settings');
debugPrint('2. Verify products are "Ready to Submit" in App Store Connect');
debugPrint('3. Check Bundle ID matches exactly');
debugPrint('4. Ensure In-App Purchase capability is enabled');
}
}
Android Specific:
class AndroidTroubleshooting {
static Future<void> diagnoseAndroidIssues() async {
debugPrint('🤖 Diagnosing Android-specific issues...');
// Check Play Store availability
try {
final store = await FlutterInappPurchase.instance.getStore();
debugPrint('Current store: $store');
if (store != 'play') {
debugPrint('❌ Not using Google Play Store');
debugPrint('💡 App must be installed from Play Store for purchases');
}
} catch (e) {
debugPrint('Store check failed: $e');
}
// Check if running on signed build
debugPrint('Build mode: ${kDebugMode ? "DEBUG" : "RELEASE"}');
if (kDebugMode) {
debugPrint('⚠️ Debug builds may not work with real products');
debugPrint('💡 Use signed build for testing real products');
}
// Common Android issues
debugPrint('\n🔧 Common Android solutions:');
debugPrint('1. Use signed APK/AAB, not debug build');
debugPrint('2. Add license testing account in Play Console');
debugPrint('3. Ensure products are "Active" in Play Console');
debugPrint('4. Upload app to at least Internal Testing track');
}
}
Debugging Tips
1. Enable verbose logging
class DebugLogging {
static void enableVerboseLogging() {
if (kDebugMode) {
// Log all purchase events
FlutterInappPurchase.purchaseUpdated.listen((purchase) {
debugPrint('📱 PURCHASE UPDATE: ${purchase?.toJson()}');
});
FlutterInappPurchase.purchaseError.listen((error) {
debugPrint('❌ PURCHASE ERROR: ${error?.toJson()}');
});
FlutterInappPurchase.connectionUpdated.listen((result) {
debugPrint('🔗 CONNECTION UPDATE: $result');
});
}
}
}
2. Log purchase events
class PurchaseEventLogger {
static void logPurchaseFlow(String step, [Map<String, dynamic>? data]) {
final timestamp = DateTime.now().toIso8601String();
debugPrint('[$timestamp] PURCHASE: $step');
if (data != null) {
data.forEach((key, value) {
debugPrint(' $key: $value');
});
}
}
// Usage
static void example() {
logPurchaseFlow('INITIATED', {'productId': 'premium'});
logPurchaseFlow('DIALOG_SHOWN');
logPurchaseFlow('COMPLETED', {'transactionId': 'txn_123'});
}
}
3. Monitor connection state
class ConnectionMonitor {
static void startMonitoring() {
Timer.periodic(Duration(seconds: 10), (timer) async {
try {
final connected = await _checkConnection();
debugPrint('🔗 Connection status: ${connected ? "CONNECTED" : "DISCONNECTED"}');
if (!connected) {
debugPrint('⚠️ Connection lost - attempting reconnect...');
await FlutterInappPurchase.instance.initConnection();
}
} catch (e) {
debugPrint('❌ Connection check failed: $e');
}
});
}
}
Testing Strategies
1. Staged testing approach
class StagedTesting {
static Future<void> runStagedTests() async {
debugPrint('🧪 Starting staged testing...');
// Stage 1: Connection test
debugPrint('\n📊 Stage 1: Connection Test');
final connected = await _testConnection();
if (!connected) return;
// Stage 2: Product loading test
debugPrint('\n📊 Stage 2: Product Loading Test');
final productsLoaded = await _testProductLoading();
if (!productsLoaded) return;
// Stage 3: Purchase flow test
debugPrint('\n📊 Stage 3: Purchase Flow Test');
await _testPurchaseFlow();
debugPrint('\n✅ All tests completed');
}
}
2. Test different scenarios
class ScenarioTesting {
static Future<void> testAllScenarios() async {
final scenarios = [
'first_time_user',
'returning_user',
'user_with_existing_purchases',
'network_interruption',
'app_backgrounded_during_purchase',
];
for (final scenario in scenarios) {
debugPrint('🎭 Testing scenario: $scenario');
await _testScenario(scenario);
}
}
}
3. Device testing matrix
class DeviceTestMatrix {
static const testMatrix = {
'iOS': [
{'version': '15.0', 'device': 'iPhone 12'},
{'version': '16.0', 'device': 'iPhone 14'},
{'version': '17.0', 'device': 'iPhone 15'},
],
'Android': [
{'version': '11', 'device': 'Pixel 5'},
{'version': '12', 'device': 'Samsung S22'},
{'version': '13', 'device': 'Pixel 7'},
],
};
static void logTestResults(String platform, String version, bool passed) {
debugPrint('📱 Test Result: $platform $version - ${passed ? "PASSED" : "FAILED"}');
}
}
Error Code Reference
class ErrorCodeReference {
static String getErrorDescription(int code) {
switch (code) {
case 0:
return 'OK - Success';
case 1:
return 'User Canceled - User pressed back or canceled a dialog';
case 2:
return 'Service Unavailable - Network connection is down';
case 3:
return 'Billing Unavailable - Billing API version is not supported';
case 4:
return 'Item Unavailable - Requested product is not available';
case 5:
return 'Developer Error - Invalid arguments provided to the API';
case 6:
return 'Error - Fatal error during the API action';
case 7:
return 'Item Already Owned - User already owns the item';
case 8:
return 'Item Not Owned - User does not own the item';
default:
return 'Unknown Error Code: $code';
}
}
static void logError(PurchaseResult error) {
debugPrint('❌ Error ${error.responseCode}: ${getErrorDescription(error.responseCode ?? -1)}');
debugPrint(' Message: ${error.message}');
debugPrint(' Debug Message: ${error.debugMessage}');
}
}
Getting Help
Bug report template
When reporting issues, please include:
**Environment:**
- flutter_inapp_purchase version: 6.0.0
- Flutter version: [run `flutter --version`]
- Platform: iOS/Android
- Device/OS version:
**Store Setup:**
- [ ] Products configured in App Store Connect/Play Console
- [ ] App uploaded to store (Internal Testing for Android)
- [ ] Test accounts configured
**Issue Description:**
[Describe what you expected vs what actually happened]
**Code Sample:**
```dart
// Minimal reproducible code
Logs:
// Error logs and debug output
// Enable verbose logging first
Troubleshooting Attempted:
- Verified product IDs match store configuration
- Tested with signed build (Android)
- Checked connection status
- Reviewed platform-specific setup
Additional Context: [Any other relevant information]
### Debug checklist
Before reporting bugs, verify:
- [ ] Product IDs match store configuration exactly
- [ ] App is properly configured in respective store
- [ ] Using signed build for testing (Android)
- [ ] Connection established before making requests
- [ ] Purchase listeners set up before purchase attempts
- [ ] Error handling implemented
- [ ] Tested on physical device
- [ ] Verbose logging enabled
For additional help:
- Check [GitHub Issues](https://github.com/hyochan/flutter_inapp_purchase/issues)
- Review [API Documentation](../api/flutter-inapp-purchase.md)
- Test with [Example App](https://github.com/hyochan/flutter_inapp_purchase/tree/main/example)