[In_App_Purchase] Avoids possible NullPointerException with background registrations. (#2014)
This PR delays the get of a reference of a Activity instance and raises an error in case of a invalid access to the method that uses it.
diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md
index e7eb3a4..fbbb870 100644
--- a/packages/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.2.1+2
+
+* Android: Require a non-null Activity to use the `launchBillingFlow` method.
+
## 0.2.1+1
* Remove skipped driver test.
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
index 16ab591..e914d2a 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
@@ -37,8 +37,8 @@
public class InAppPurchasePlugin implements MethodCallHandler {
private static final String TAG = "InAppPurchasePlugin";
private @Nullable BillingClient billingClient;
- private final Activity activity;
- private final Context context;
+ private final Registrar registrar;
+ private final Context applicationContext;
private final MethodChannel channel;
@VisibleForTesting
@@ -69,13 +69,12 @@
public static void registerWith(Registrar registrar) {
final MethodChannel channel =
new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase");
- channel.setMethodCallHandler(
- new InAppPurchasePlugin(registrar.context(), registrar.activity(), channel));
+ channel.setMethodCallHandler(new InAppPurchasePlugin(registrar, channel));
}
- public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) {
- this.context = context;
- this.activity = activity;
+ public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) {
+ this.applicationContext = registrar.context();
+ this.registrar = registrar;
this.channel = channel;
}
@@ -114,16 +113,17 @@
}
@VisibleForTesting
- /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) {
+ /*package*/ InAppPurchasePlugin(
+ Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) {
this.billingClient = billingClient;
this.channel = channel;
- this.context = null;
- this.activity = null;
+ this.applicationContext = registrar.context();
+ this.registrar = registrar;
}
private void startConnection(final int handle, final Result result) {
if (billingClient == null) {
- billingClient = buildBillingClient(context, channel);
+ billingClient = buildBillingClient(applicationContext, channel);
}
billingClient.startConnection(
@@ -201,6 +201,17 @@
null);
return;
}
+ final Activity activity = registrar.activity();
+
+ if (activity == null) {
+ result.error(
+ "ACTIVITY_UNAVAILABLE",
+ "Details for sku "
+ + sku
+ + " are not available. This method must be run with the app in foreground.",
+ null);
+ return;
+ }
BillingFlowParams.Builder paramsBuilder =
BillingFlowParams.newBuilder().setSkuDetails(skuDetails);
diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
index 4792505..41a6078 100644
--- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
+++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java
@@ -28,6 +28,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
+import android.content.Context;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClient.BillingResponse;
@@ -41,9 +43,11 @@
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
+import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
+import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.inapppurchase.InAppPurchasePlugin.PluginPurchaseListener;
import java.util.HashMap;
import java.util.List;
@@ -60,11 +64,16 @@
@Mock BillingClient mockBillingClient;
@Mock MethodChannel mockMethodChannel;
@Spy Result result;
+ @Mock PluginRegistry.Registrar registrar;
+ @Mock Activity activity;
+ @Mock BinaryMessenger messenger;
+ @Mock Context context;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel);
+ when(registrar.context()).thenReturn(context);
+ plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel);
}
@Test
@@ -219,6 +228,7 @@
@Test
public void launchBillingFlow_ok_nullAccountId() {
+ when(registrar.activity()).thenReturn(activity);
// Fetch the sku details first and then prepare the launch billing flow call
String skuId = "foo";
queryForSkus(singletonList(skuId));
@@ -246,7 +256,26 @@
}
@Test
+ public void launchBillingFlow_ok_null_Activity() {
+ // Fetch the sku details first and then prepare the launch billing flow call
+ String skuId = "foo";
+ String accountId = "account";
+ queryForSkus(singletonList(skuId));
+ HashMap<String, Object> arguments = new HashMap<>();
+ arguments.put("sku", skuId);
+ arguments.put("accountId", accountId);
+ MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
+
+ plugin.onMethodCall(launchCall, result);
+
+ // Verify we pass the response code to result
+ verify(result).error(contains("ACTIVITY_UNAVAILABLE"), contains("foreground"), any());
+ verify(result, never()).success(any());
+ }
+
+ @Test
public void launchBillingFlow_ok_AccountId() {
+ when(registrar.activity()).thenReturn(activity);
// Fetch the sku details first and query the method call
String skuId = "foo";
String accountId = "account";
diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml
index 1bd07cd..d3a8cf0 100644
--- a/packages/in_app_purchase/pubspec.yaml
+++ b/packages/in_app_purchase/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
author: Flutter Team <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
-version: 0.2.1+1
+version: 0.2.1+2
dependencies:
@@ -33,4 +33,3 @@
environment:
sdk: ">=2.0.0-dev.28.0 <3.0.0"
flutter: ">=1.5.0 <2.0.0"
-