Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit a941127

Browse files
juliocbcottaMichael Klimushyn
authored andcommitted
[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.
1 parent b47754c commit a941127

File tree

4 files changed

+57
-14
lines changed

4 files changed

+57
-14
lines changed

packages/in_app_purchase/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.2.1+2
2+
3+
* Android: Require a non-null Activity to use the `launchBillingFlow` method.
4+
15
## 0.2.1+1
26

37
* Remove skipped driver test.

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
public class InAppPurchasePlugin implements MethodCallHandler {
3838
private static final String TAG = "InAppPurchasePlugin";
3939
private @Nullable BillingClient billingClient;
40-
private final Activity activity;
41-
private final Context context;
40+
private final Registrar registrar;
41+
private final Context applicationContext;
4242
private final MethodChannel channel;
4343

4444
@VisibleForTesting
@@ -69,13 +69,12 @@ static final class MethodNames {
6969
public static void registerWith(Registrar registrar) {
7070
final MethodChannel channel =
7171
new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase");
72-
channel.setMethodCallHandler(
73-
new InAppPurchasePlugin(registrar.context(), registrar.activity(), channel));
72+
channel.setMethodCallHandler(new InAppPurchasePlugin(registrar, channel));
7473
}
7574

76-
public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) {
77-
this.context = context;
78-
this.activity = activity;
75+
public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) {
76+
this.applicationContext = registrar.context();
77+
this.registrar = registrar;
7978
this.channel = channel;
8079
}
8180

@@ -114,16 +113,17 @@ public void onMethodCall(MethodCall call, Result result) {
114113
}
115114

116115
@VisibleForTesting
117-
/*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) {
116+
/*package*/ InAppPurchasePlugin(
117+
Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) {
118118
this.billingClient = billingClient;
119119
this.channel = channel;
120-
this.context = null;
121-
this.activity = null;
120+
this.applicationContext = registrar.context();
121+
this.registrar = registrar;
122122
}
123123

124124
private void startConnection(final int handle, final Result result) {
125125
if (billingClient == null) {
126-
billingClient = buildBillingClient(context, channel);
126+
billingClient = buildBillingClient(applicationContext, channel);
127127
}
128128

129129
billingClient.startConnection(
@@ -201,6 +201,17 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re
201201
null);
202202
return;
203203
}
204+
final Activity activity = registrar.activity();
205+
206+
if (activity == null) {
207+
result.error(
208+
"ACTIVITY_UNAVAILABLE",
209+
"Details for sku "
210+
+ sku
211+
+ " are not available. This method must be run with the app in foreground.",
212+
null);
213+
return;
214+
}
204215

205216
BillingFlowParams.Builder paramsBuilder =
206217
BillingFlowParams.newBuilder().setSkuDetails(skuDetails);

packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import static org.mockito.Mockito.verify;
2929
import static org.mockito.Mockito.when;
3030

31+
import android.app.Activity;
32+
import android.content.Context;
3133
import androidx.annotation.Nullable;
3234
import com.android.billingclient.api.BillingClient;
3335
import com.android.billingclient.api.BillingClient.BillingResponse;
@@ -41,9 +43,11 @@
4143
import com.android.billingclient.api.SkuDetails;
4244
import com.android.billingclient.api.SkuDetailsParams;
4345
import com.android.billingclient.api.SkuDetailsResponseListener;
46+
import io.flutter.plugin.common.BinaryMessenger;
4447
import io.flutter.plugin.common.MethodCall;
4548
import io.flutter.plugin.common.MethodChannel;
4649
import io.flutter.plugin.common.MethodChannel.Result;
50+
import io.flutter.plugin.common.PluginRegistry;
4751
import io.flutter.plugins.inapppurchase.InAppPurchasePlugin.PluginPurchaseListener;
4852
import java.util.HashMap;
4953
import java.util.List;
@@ -60,11 +64,16 @@ public class InAppPurchasePluginTest {
6064
@Mock BillingClient mockBillingClient;
6165
@Mock MethodChannel mockMethodChannel;
6266
@Spy Result result;
67+
@Mock PluginRegistry.Registrar registrar;
68+
@Mock Activity activity;
69+
@Mock BinaryMessenger messenger;
70+
@Mock Context context;
6371

6472
@Before
6573
public void setUp() {
6674
MockitoAnnotations.initMocks(this);
67-
plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel);
75+
when(registrar.context()).thenReturn(context);
76+
plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel);
6877
}
6978

7079
@Test
@@ -219,6 +228,7 @@ public void querySkuDetailsAsync_clientDisconnected() {
219228

220229
@Test
221230
public void launchBillingFlow_ok_nullAccountId() {
231+
when(registrar.activity()).thenReturn(activity);
222232
// Fetch the sku details first and then prepare the launch billing flow call
223233
String skuId = "foo";
224234
queryForSkus(singletonList(skuId));
@@ -245,8 +255,27 @@ public void launchBillingFlow_ok_nullAccountId() {
245255
verify(result, times(1)).success(responseCode);
246256
}
247257

258+
@Test
259+
public void launchBillingFlow_ok_null_Activity() {
260+
// Fetch the sku details first and then prepare the launch billing flow call
261+
String skuId = "foo";
262+
String accountId = "account";
263+
queryForSkus(singletonList(skuId));
264+
HashMap<String, Object> arguments = new HashMap<>();
265+
arguments.put("sku", skuId);
266+
arguments.put("accountId", accountId);
267+
MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
268+
269+
plugin.onMethodCall(launchCall, result);
270+
271+
// Verify we pass the response code to result
272+
verify(result).error(contains("ACTIVITY_UNAVAILABLE"), contains("foreground"), any());
273+
verify(result, never()).success(any());
274+
}
275+
248276
@Test
249277
public void launchBillingFlow_ok_AccountId() {
278+
when(registrar.activity()).thenReturn(activity);
250279
// Fetch the sku details first and query the method call
251280
String skuId = "foo";
252281
String accountId = "account";

packages/in_app_purchase/pubspec.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: in_app_purchase
22
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
33
author: Flutter Team <flutter-dev@googlegroups.com>
44
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
5-
version: 0.2.1+1
5+
version: 0.2.1+2
66

77

88
dependencies:
@@ -33,4 +33,3 @@ flutter:
3333
environment:
3434
sdk: ">=2.0.0-dev.28.0 <3.0.0"
3535
flutter: ">=1.5.0 <2.0.0"
36-

0 commit comments

Comments
 (0)