[in_app_purchase] Added BillingClient.isFeatureSupported (#4063)
* Added BillingClient.isFeatureSupported
* pubspec and changelog
diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
index 6175462..526fdba 100644
--- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.3
+
+Added support for isFeatureSupported in the BillingClientWrapper and in InAppPurchaseAndroidPlatformAddition.
+
## 0.1.2
* Added support for the obfuscatedAccountId and obfuscatedProfileId in the PurchaseWrapper.
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
index e4719f0..ad53439 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
@@ -38,6 +38,7 @@
"BillingClient#consumeAsync(String, ConsumeResponseListener)";
static final String ACKNOWLEDGE_PURCHASE =
"BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";
+ static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)";
private MethodNames() {};
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
index cfcb81a..473c21d 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
@@ -145,6 +145,9 @@
case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE:
acknowledgePurchase((String) call.argument("purchaseToken"), result);
break;
+ case InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED:
+ isFeatureSupported((String) call.argument("feature"), result);
+ break;
default:
result.notImplemented();
}
@@ -379,4 +382,13 @@
result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null);
return true;
}
+
+ private void isFeatureSupported(String feature, MethodChannel.Result result) {
+ if (billingClientError(result)) {
+ return;
+ }
+ assert billingClient != null;
+ BillingResult billingResult = billingClient.isFeatureSupported(feature);
+ result.success(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK);
+ }
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
index 4d7a022..7465e6a 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
@@ -7,6 +7,7 @@
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.END_CONNECTION;
+import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_READY;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_BILLING_FLOW;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_DISCONNECT;
@@ -729,6 +730,44 @@
verify(mockBillingClient).endConnection();
}
+ @Test
+ public void isFutureSupported_true() {
+ mockStartConnection();
+ final String feature = "subscriptions";
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("feature", feature);
+
+ BillingResult billingResult =
+ BillingResult.newBuilder()
+ .setResponseCode(BillingClient.BillingResponseCode.OK)
+ .setDebugMessage("dummy debug message")
+ .build();
+
+ MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments);
+ when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult);
+ methodChannelHandler.onMethodCall(call, result);
+ verify(result).success(true);
+ }
+
+ @Test
+ public void isFutureSupported_false() {
+ mockStartConnection();
+ final String feature = "subscriptions";
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("feature", feature);
+
+ BillingResult billingResult =
+ BillingResult.newBuilder()
+ .setResponseCode(BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED)
+ .setDebugMessage("dummy debug message")
+ .build();
+
+ MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments);
+ when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult);
+ methodChannelHandler.onMethodCall(call, result);
+ verify(result).success(false);
+ }
+
private ArgumentCaptor<BillingClientStateListener> mockStartConnection() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("handle", 1);
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart
index c5726c4..cb8cb75 100644
--- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart
@@ -147,6 +147,7 @@
_buildConnectionCheckTile(),
_buildProductList(),
_buildConsumableBox(),
+ _FeatureCard(),
],
),
);
@@ -434,3 +435,59 @@
return oldSubscription;
}
}
+
+class _FeatureCard extends StatelessWidget {
+ final InAppPurchaseAndroidPlatformAddition addition =
+ InAppPurchasePlatformAddition.instance
+ as InAppPurchaseAndroidPlatformAddition;
+
+ _FeatureCard({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: <Widget>[
+ ListTile(title: Text('Available features')),
+ Divider(),
+ for (BillingClientFeature feature in BillingClientFeature.values)
+ _buildFeatureWidget(feature),
+ ]));
+ }
+
+ Widget _buildFeatureWidget(BillingClientFeature feature) {
+ return FutureBuilder<bool>(
+ future: addition.isFeatureSupported(feature),
+ builder: (context, snapshot) {
+ Color color = Colors.grey;
+ bool? data = snapshot.data;
+ if (data != null) {
+ color = data ? Colors.green : Colors.red;
+ }
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(16.0, 4.0, 16.0, 4.0),
+ child: Text(
+ _featureToString(feature),
+ style: TextStyle(color: color),
+ ),
+ );
+ },
+ );
+ }
+
+ String _featureToString(BillingClientFeature feature) {
+ switch (feature) {
+ case BillingClientFeature.inAppItemsOnVR:
+ return 'inAppItemsOnVR';
+ case BillingClientFeature.priceChangeConfirmation:
+ return 'priceChangeConfirmation';
+ case BillingClientFeature.subscriptions:
+ return 'subscriptions';
+ case BillingClientFeature.subscriptionsOnVR:
+ return 'subscriptionsOnVR';
+ case BillingClientFeature.subscriptionsUpdate:
+ return 'subscriptionsUpdate';
+ }
+ }
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart
index 1f43b3a..cf08fa9 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart
@@ -301,6 +301,16 @@
<String, dynamic>{});
}
+ /// Checks if the specified feature or capability is supported by the Play Store.
+ /// Call this to check if a [BillingClientFeature] is supported by the device.
+ Future<bool> isFeatureSupported(BillingClientFeature feature) async {
+ var result = await channel.invokeMethod<bool>(
+ 'BillingClient#isFeatureSupported(String)', <String, dynamic>{
+ 'feature': BillingClientFeatureConverter().toJson(feature),
+ });
+ return result ?? false;
+ }
+
/// The method call handler for [channel].
@visibleForTesting
Future<void> callHandler(MethodCall call) async {
@@ -446,3 +456,31 @@
@JsonValue(4)
deferred,
}
+
+/// Features/capabilities supported by [BillingClient.isFeatureSupported()](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType).
+enum BillingClientFeature {
+ // WARNING: Changes to this class need to be reflected in our generated code.
+ // Run `flutter packages pub run build_runner watch` to rebuild and watch for
+ // further changes.
+
+ // JsonValues need to match constant values defined in https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType#summary
+ /// Purchase/query for in-app items on VR.
+ @JsonValue('inAppItemsOnVr')
+ inAppItemsOnVR,
+
+ /// Launch a price change confirmation flow.
+ @JsonValue('priceChangeConfirmation')
+ priceChangeConfirmation,
+
+ /// Purchase/query for subscriptions.
+ @JsonValue('subscriptions')
+ subscriptions,
+
+ /// Purchase/query for subscriptions on VR.
+ @JsonValue('subscriptionsOnVr')
+ subscriptionsOnVR,
+
+ /// Subscriptions update/replace.
+ @JsonValue('subscriptionsUpdate')
+ subscriptionsUpdate
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.dart
index 46d6843..7ff3330 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.dart
@@ -72,15 +72,6 @@
int toJson(ProrationMode object) => _$ProrationModeEnumMap[object]!;
}
-// Define a class so we generate serializer helper methods for the enums
-@JsonSerializable()
-class _SerializedEnums {
- late BillingResponse response;
- late SkuType type;
- late PurchaseStateWrapper purchaseState;
- late ProrationMode prorationMode;
-}
-
/// Serializer for [PurchaseStateWrapper].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
@@ -118,3 +109,34 @@
}
}
}
+
+/// Serializer for [BillingClientFeature].
+///
+/// Use these in `@JsonSerializable()` classes by annotating them with
+/// `@BillingClientFeatureConverter()`.
+class BillingClientFeatureConverter
+ implements JsonConverter<BillingClientFeature, String> {
+ /// Default const constructor.
+ const BillingClientFeatureConverter();
+
+ @override
+ BillingClientFeature fromJson(String json) {
+ return _$enumDecode<BillingClientFeature, dynamic>(
+ _$BillingClientFeatureEnumMap.cast<BillingClientFeature, dynamic>(),
+ json);
+ }
+
+ @override
+ String toJson(BillingClientFeature object) =>
+ _$BillingClientFeatureEnumMap[object]!;
+}
+
+// Define a class so we generate serializer helper methods for the enums
+@JsonSerializable()
+class _SerializedEnums {
+ late BillingResponse response;
+ late SkuType type;
+ late PurchaseStateWrapper purchaseState;
+ late ProrationMode prorationMode;
+ late BillingClientFeature billingClientFeature;
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.g.dart
index 4186a2a..8d667d0 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.g.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/enum_converters.g.dart
@@ -13,7 +13,9 @@
..purchaseState =
_$enumDecode(_$PurchaseStateWrapperEnumMap, json['purchaseState'])
..prorationMode =
- _$enumDecode(_$ProrationModeEnumMap, json['prorationMode']);
+ _$enumDecode(_$ProrationModeEnumMap, json['prorationMode'])
+ ..billingClientFeature = _$enumDecode(
+ _$BillingClientFeatureEnumMap, json['billingClientFeature']);
}
Map<String, dynamic> _$_SerializedEnumsToJson(_SerializedEnums instance) =>
@@ -22,6 +24,8 @@
'type': _$SkuTypeEnumMap[instance.type],
'purchaseState': _$PurchaseStateWrapperEnumMap[instance.purchaseState],
'prorationMode': _$ProrationModeEnumMap[instance.prorationMode],
+ 'billingClientFeature':
+ _$BillingClientFeatureEnumMap[instance.billingClientFeature],
};
K _$enumDecode<K, V>(
@@ -83,3 +87,11 @@
ProrationMode.immediateWithoutProration: 3,
ProrationMode.deferred: 4,
};
+
+const _$BillingClientFeatureEnumMap = {
+ BillingClientFeature.inAppItemsOnVR: 'inAppItemsOnVr',
+ BillingClientFeature.priceChangeConfirmation: 'priceChangeConfirmation',
+ BillingClientFeature.subscriptions: 'subscriptions',
+ BillingClientFeature.subscriptionsOnVR: 'subscriptionsOnVr',
+ BillingClientFeature.subscriptionsUpdate: 'subscriptionsUpdate',
+};
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart
index 84f8b9e..fc4ab7c 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart
@@ -135,4 +135,10 @@
return QueryPurchaseDetailsResponse(
pastPurchases: pastPurchases, error: error);
}
+
+ /// Checks if the specified feature or capability is supported by the Play Store.
+ /// Call this to check if a [BillingClientFeature] is supported by the device.
+ Future<bool> isFeatureSupported(BillingClientFeature feature) async {
+ return _billingClient.isFeatureSupported(feature);
+ }
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
index 900fa43..58069b0 100644
--- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs.
repository: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase/in_app_purchase_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.1.2
+version: 0.1.3
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart
index ec72897..6ab1641 100644
--- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart
@@ -544,4 +544,34 @@
debugMessage: kInvalidBillingResultErrorMessage)));
});
});
+
+ group('isFeatureSupported', () {
+ const String isFeatureSupportedMethodName =
+ 'BillingClient#isFeatureSupported(String)';
+ test('isFeatureSupported returns false', () async {
+ late Map<Object?, Object?> arguments;
+ stubPlatform.addResponse(
+ name: isFeatureSupportedMethodName,
+ value: false,
+ additionalStepBeforeReturn: (value) => arguments = value,
+ );
+ final bool isSupported = await billingClient
+ .isFeatureSupported(BillingClientFeature.subscriptions);
+ expect(isSupported, isFalse);
+ expect(arguments['feature'], equals('subscriptions'));
+ });
+
+ test('isFeatureSupported returns true', () async {
+ late Map<Object?, Object?> arguments;
+ stubPlatform.addResponse(
+ name: isFeatureSupportedMethodName,
+ value: true,
+ additionalStepBeforeReturn: (value) => arguments = value,
+ );
+ final bool isSupported = await billingClient
+ .isFeatureSupported(BillingClientFeature.subscriptions);
+ expect(isSupported, isTrue);
+ expect(arguments['feature'], equals('subscriptions'));
+ });
+ });
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart
index 36958d2..0ef17e7 100644
--- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart
@@ -141,4 +141,34 @@
});
});
});
+
+ group('isFeatureSupported', () {
+ const String isFeatureSupportedMethodName =
+ 'BillingClient#isFeatureSupported(String)';
+ test('isFeatureSupported returns false', () async {
+ late Map<Object?, Object?> arguments;
+ stubPlatform.addResponse(
+ name: isFeatureSupportedMethodName,
+ value: false,
+ additionalStepBeforeReturn: (value) => arguments = value,
+ );
+ final bool isSupported = await iapAndroidPlatformAddition
+ .isFeatureSupported(BillingClientFeature.subscriptions);
+ expect(isSupported, isFalse);
+ expect(arguments['feature'], equals('subscriptions'));
+ });
+
+ test('isFeatureSupported returns true', () async {
+ late Map<Object?, Object?> arguments;
+ stubPlatform.addResponse(
+ name: isFeatureSupportedMethodName,
+ value: true,
+ additionalStepBeforeReturn: (value) => arguments = value,
+ );
+ final bool isSupported = await iapAndroidPlatformAddition
+ .isFeatureSupported(BillingClientFeature.subscriptions);
+ expect(isSupported, isTrue);
+ expect(arguments['feature'], equals('subscriptions'));
+ });
+ });
}