From 52cd0a0bbaf3f069f1565388adb7b4a141d0f3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Sun, 25 Aug 2019 22:59:10 -0300 Subject: [PATCH 01/11] Do not register plugin if Activity is null. --- packages/in_app_purchase/CHANGELOG.md | 4 +++ .../inapppurchase/InAppPurchasePlugin.java | 32 ++++++++++++------- packages/in_app_purchase/pubspec.yaml | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 891818199c1a..0b9f6214c62e 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+1 + +* Android: Do not register if Activity is null. + ## 0.2.1 * iOS: Add currencyCode to priceLocale on productDetails. 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 16ab5916c908..e7f3f3d62c71 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 @@ -11,6 +11,7 @@ import android.app.Activity; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; @@ -67,10 +68,13 @@ static final class MethodNames { /** Plugin registration. */ public static void registerWith(Registrar registrar) { + final Activity activity = registrar.activity(); + if (activity == null) { + return; + } 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.context(), activity, channel)); } public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) { @@ -80,33 +84,37 @@ public InAppPurchasePlugin(Context context, Activity activity, MethodChannel cha } @Override - public void onMethodCall(MethodCall call, Result result) { + public void onMethodCall(MethodCall call, @NonNull Result result) { + final String skuType = call.argument("skuType"); switch (call.method) { case MethodNames.IS_READY: isReady(result); break; case MethodNames.START_CONNECTION: - startConnection((int) call.argument("handle"), result); + final Integer handle = call.argument("handle"); + startConnection(handle, result); break; case MethodNames.END_CONNECTION: endConnection(result); break; case MethodNames.QUERY_SKU_DETAILS: - querySkuDetailsAsync( - (String) call.argument("skuType"), (List) call.argument("skusList"), result); + final List skusList = call.argument("skusList"); + querySkuDetailsAsync(skuType, skusList, result); break; case MethodNames.LAUNCH_BILLING_FLOW: - launchBillingFlow( - (String) call.argument("sku"), (String) call.argument("accountId"), result); + final String sku = call.argument("sku"); + final String accountId = call.argument("accountId"); + launchBillingFlow(sku, accountId, result); break; case MethodNames.QUERY_PURCHASES: - queryPurchases((String) call.argument("skuType"), result); + queryPurchases(skuType, result); break; case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: - queryPurchaseHistoryAsync((String) call.argument("skuType"), result); + queryPurchaseHistoryAsync(skuType, result); break; case MethodNames.CONSUME_PURCHASE_ASYNC: - consumeAsync((String) call.argument("purchaseToken"), result); + final String purchaseToken = call.argument("purchaseToken"); + consumeAsync(purchaseToken, result); break; default: result.notImplemented(); @@ -121,7 +129,7 @@ public void onMethodCall(MethodCall call, Result result) { this.activity = null; } - private void startConnection(final int handle, final Result result) { + private void startConnection(final Integer handle, final Result result) { if (billingClient == null) { billingClient = buildBillingClient(context, channel); } diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index a398d945fa03..1bd07cda704b 100644 --- a/packages/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase 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 homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase -version: 0.2.1 +version: 0.2.1+1 dependencies: From da055d5f700d4f7a333fee921554a077dd66ca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:11:42 -0300 Subject: [PATCH 02/11] Revert unrelated changes. --- .../inapppurchase/InAppPurchasePlugin.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) 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 e7f3f3d62c71..19dd3070d692 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 @@ -11,7 +11,6 @@ import android.app.Activity; import android.content.Context; import android.util.Log; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; @@ -84,37 +83,33 @@ public InAppPurchasePlugin(Context context, Activity activity, MethodChannel cha } @Override - public void onMethodCall(MethodCall call, @NonNull Result result) { - final String skuType = call.argument("skuType"); + public void onMethodCall(MethodCall call, Result result) { switch (call.method) { case MethodNames.IS_READY: isReady(result); break; case MethodNames.START_CONNECTION: - final Integer handle = call.argument("handle"); - startConnection(handle, result); + startConnection((int) call.argument("handle"), result); break; case MethodNames.END_CONNECTION: endConnection(result); break; case MethodNames.QUERY_SKU_DETAILS: - final List skusList = call.argument("skusList"); - querySkuDetailsAsync(skuType, skusList, result); + querySkuDetailsAsync( + (String) call.argument("skuType"), (List) call.argument("skusList"), result); break; case MethodNames.LAUNCH_BILLING_FLOW: - final String sku = call.argument("sku"); - final String accountId = call.argument("accountId"); - launchBillingFlow(sku, accountId, result); + launchBillingFlow( + (String) call.argument("sku"), (String) call.argument("accountId"), result); break; case MethodNames.QUERY_PURCHASES: - queryPurchases(skuType, result); + queryPurchases((String) call.argument("skuType"), result); break; case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: - queryPurchaseHistoryAsync(skuType, result); + queryPurchaseHistoryAsync((String) call.argument("skuType"), result); break; case MethodNames.CONSUME_PURCHASE_ASYNC: - final String purchaseToken = call.argument("purchaseToken"); - consumeAsync(purchaseToken, result); + consumeAsync((String) call.argument("purchaseToken"), result); break; default: result.notImplemented(); @@ -129,7 +124,7 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { this.activity = null; } - private void startConnection(final Integer handle, final Result result) { + private void startConnection(final int handle, final Result result) { if (billingClient == null) { billingClient = buildBillingClient(context, channel); } From bcf733a045e5a601623508dd33c167ade44fd6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:12:07 -0300 Subject: [PATCH 03/11] Adds UnitTest for registration. --- .../InAppPurchasePluginTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 4792505b4476..34ce31943542 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,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient.BillingResponse; @@ -41,9 +42,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,6 +63,9 @@ public class InAppPurchasePluginTest { @Mock BillingClient mockBillingClient; @Mock MethodChannel mockMethodChannel; @Spy Result result; + @Mock PluginRegistry.Registrar registrar; + @Mock Activity activity; + @Mock BinaryMessenger messenger; @Before public void setUp() { @@ -67,6 +73,20 @@ public void setUp() { plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel); } + @Test + public void doNotRegisterWithoutActivityAvailable() { + InAppPurchasePlugin.registerWith(registrar); + verify(registrar, never()).messenger(); + } + + @Test + public void registerWithoutActivityAvailable() { + when(registrar.messenger()).thenReturn(messenger); + when(registrar.activity()).thenReturn(activity); + InAppPurchasePlugin.registerWith(registrar); + verify(registrar).messenger(); + } + @Test public void invalidMethod() { MethodCall call = new MethodCall("invalid", null); From ed97762b72e092e389404b8f3bac885f5fea48f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:39:02 -0300 Subject: [PATCH 04/11] Improved testability. --- .../inapppurchase/BillingClientFactory.java | 14 ++++++ .../inapppurchase/InAppPurchasePlugin.java | 46 ++++--------------- .../inapppurchase/PluginPurchaseListener.java | 27 +++++++++++ .../InAppPurchasePluginTest.java | 29 ++++++++---- 4 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java create mode 100644 packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java new file mode 100644 index 000000000000..e987984374fb --- /dev/null +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -0,0 +1,14 @@ +package io.flutter.plugins.inapppurchase; + +import android.content.Context; +import com.android.billingclient.api.BillingClient; +import io.flutter.plugin.common.MethodChannel; + +public class BillingClientFactory { + + public BillingClient createBillingClient(Context context, MethodChannel channel) { + return BillingClient.newBuilder(context) + .setListener(new PluginPurchaseListener(channel)) + .build(); + } +} 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 19dd3070d692..9f4e0ac028d1 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 @@ -9,7 +9,6 @@ import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; import android.app.Activity; -import android.content.Context; import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -19,7 +18,6 @@ import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryResponseListener; -import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; @@ -37,8 +35,8 @@ public class InAppPurchasePlugin implements MethodCallHandler { private static final String TAG = "InAppPurchasePlugin"; private @Nullable BillingClient billingClient; + private BillingClientFactory factory; private final Activity activity; - private final Context context; private final MethodChannel channel; @VisibleForTesting @@ -73,11 +71,14 @@ 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(), activity, channel)); + final BillingClientFactory factory = new BillingClientFactory(); + final InAppPurchasePlugin plugin = new InAppPurchasePlugin(factory, activity, channel); + channel.setMethodCallHandler(plugin); } - public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) { - this.context = context; + public InAppPurchasePlugin( + BillingClientFactory factory, Activity activity, MethodChannel channel) { + this.factory = factory; this.activity = activity; this.channel = channel; } @@ -116,17 +117,9 @@ public void onMethodCall(MethodCall call, Result result) { } } - @VisibleForTesting - /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) { - this.billingClient = billingClient; - this.channel = channel; - this.context = null; - this.activity = null; - } - private void startConnection(final int handle, final Result result) { if (billingClient == null) { - billingClient = buildBillingClient(context, channel); + billingClient = factory.createBillingClient(activity, channel); } billingClient.startConnection( @@ -274,27 +267,4 @@ private boolean billingClientError(Result result) { result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); return true; } - - private static BillingClient buildBillingClient(Context context, MethodChannel channel) { - return BillingClient.newBuilder(context) - .setListener(new PluginPurchaseListener(channel)) - .build(); - } - - @VisibleForTesting - /*package*/ static class PluginPurchaseListener implements PurchasesUpdatedListener { - private final MethodChannel channel; - - PluginPurchaseListener(MethodChannel channel) { - this.channel = channel; - } - - @Override - public void onPurchasesUpdated(int responseCode, @Nullable List purchases) { - final Map callbackArgs = new HashMap<>(); - callbackArgs.put("responseCode", responseCode); - callbackArgs.put("purchasesList", fromPurchasesList(purchases)); - channel.invokeMethod(MethodNames.ON_PURCHASES_UPDATED, callbackArgs); - } - } } diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java new file mode 100644 index 000000000000..f1de27eaacc8 --- /dev/null +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java @@ -0,0 +1,27 @@ +package io.flutter.plugins.inapppurchase; + +import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; + +import androidx.annotation.Nullable; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class PluginPurchaseListener implements PurchasesUpdatedListener { + private final MethodChannel channel; + + PluginPurchaseListener(MethodChannel channel) { + this.channel = channel; + } + + @Override + public void onPurchasesUpdated(int responseCode, @Nullable List purchases) { + final Map callbackArgs = new HashMap<>(); + callbackArgs.put("responseCode", responseCode); + callbackArgs.put("purchasesList", fromPurchasesList(purchases)); + channel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED, callbackArgs); + } +} 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 34ce31943542..3e0565842c4b 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 @@ -47,7 +47,6 @@ 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; import java.util.Map; @@ -66,11 +65,14 @@ public class InAppPurchasePluginTest { @Mock PluginRegistry.Registrar registrar; @Mock Activity activity; @Mock BinaryMessenger messenger; + @Mock BillingClientFactory factory; @Before public void setUp() { MockitoAnnotations.initMocks(this); - plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel); + when(factory.createBillingClient(any(), any())).thenReturn(mockBillingClient); + + plugin = new InAppPurchasePlugin(factory, activity, mockMethodChannel); } @Test @@ -96,6 +98,7 @@ public void invalidMethod() { @Test public void isReady_true() { + mockStartConnection(); MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(true); plugin.onMethodCall(call, result); @@ -104,6 +107,7 @@ public void isReady_true() { @Test public void isReady_false() { + mockStartConnection(); MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(false); plugin.onMethodCall(call, result); @@ -124,14 +128,7 @@ public void isReady_clientDisconnected() { @Test public void startConnection() { - Map arguments = new HashMap<>(); - arguments.put("handle", 1); - MethodCall call = new MethodCall(START_CONNECTION, arguments); - ArgumentCaptor captor = - ArgumentCaptor.forClass(BillingClientStateListener.class); - doNothing().when(mockBillingClient).startConnection(captor.capture()); - - plugin.onMethodCall(call, result); + ArgumentCaptor captor = mockStartConnection(); verify(result, never()).success(any()); captor.getValue().onBillingSetupFinished(100); @@ -443,6 +440,18 @@ public void consumeAsync() { verify(result, times(1)).success(responseCode); } + private ArgumentCaptor mockStartConnection() { + Map arguments = new HashMap<>(); + arguments.put("handle", 1); + MethodCall call = new MethodCall(START_CONNECTION, arguments); + ArgumentCaptor captor = + ArgumentCaptor.forClass(BillingClientStateListener.class); + doNothing().when(mockBillingClient).startConnection(captor.capture()); + + plugin.onMethodCall(call, result); + return captor; + } + private void establishConnectedBillingClient( @Nullable Map arguments, @Nullable Result result) { if (arguments == null) { From 469a7ac3b8c59afc3bd65822ec2acd1abead6990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:48:39 -0300 Subject: [PATCH 05/11] Updated CHANGELOG.md and pubspec.yaml --- packages/in_app_purchase/CHANGELOG.md | 4 ++++ packages/in_app_purchase/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 0b9f6214c62e..7a638a1fa99d 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+2 + +* Android : Improved testability. + ## 0.2.1+1 * Android: Do not register if Activity is null. diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index 1bd07cda704b..50c07237eeee 100644 --- a/packages/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase 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 homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase -version: 0.2.1+1 +version: 0.2.1+2 dependencies: From cdaea1525fdf099e4f60efab8a1ea9df85a83655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 22:16:27 -0300 Subject: [PATCH 06/11] Allow background registration and adds check for Activity nullability in a more specific method. --- .../inapppurchase/InAppPurchasePlugin.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) 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 19dd3070d692..d3d8932983f2 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 @@ -67,18 +67,14 @@ static final class MethodNames { /** Plugin registration. */ public static void registerWith(Registrar registrar) { - final Activity activity = registrar.activity(); - if (activity == null) { - return; - } final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - channel.setMethodCallHandler(new InAppPurchasePlugin(registrar.context(), 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().getApplicationContext(); + this.registrar = registrar; this.channel = channel; } @@ -120,13 +116,13 @@ public void onMethodCall(MethodCall call, Result result) { /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) { this.billingClient = billingClient; this.channel = channel; - this.context = null; - this.activity = null; + this.applicationContext = null; + this.registrar = null; } private void startConnection(final int handle, final Result result) { if (billingClient == null) { - billingClient = buildBillingClient(context, channel); + billingClient = buildBillingClient(applicationContext, channel); } billingClient.startConnection( @@ -204,6 +200,15 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re null); return; } + final Activity activity = registrar.activity(); + + if (activity == null) { + result.error( + "UNAVAILABLE", + "Details for sku " + sku + " are not available. This method requires to be run with the app in foreground.", + null); + return; + } BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder().setSkuDetails(skuDetails); From 65a6a1a5f730a967fe83ec57290b9ec8859f18f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 22:30:52 -0300 Subject: [PATCH 07/11] update tests and changelog. --- packages/in_app_purchase/CHANGELOG.md | 2 +- .../inapppurchase/InAppPurchasePlugin.java | 17 ++++---- .../InAppPurchasePluginTest.java | 39 ++++++++++++------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 0b9f6214c62e..a6b00383e6c6 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.2.1+1 -* Android: Do not register if Activity is null. +* Android: Require Activity only to use launchBillingFlow method. ## 0.2.1 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 d3d8932983f2..cfccb6c82105 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 @@ -73,7 +73,7 @@ public static void registerWith(Registrar registrar) { } public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) { - this.applicationContext = registrar.context().getApplicationContext(); + this.applicationContext = registrar.context(); this.registrar = registrar; this.channel = channel; } @@ -113,11 +113,12 @@ public void onMethodCall(MethodCall call, Result result) { } @VisibleForTesting - /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) { + /*package*/ InAppPurchasePlugin( + Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) { this.billingClient = billingClient; this.channel = channel; - this.applicationContext = null; - this.registrar = null; + this.applicationContext = registrar.context(); + this.registrar = registrar; } private void startConnection(final int handle, final Result result) { @@ -204,9 +205,11 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re if (activity == null) { result.error( - "UNAVAILABLE", - "Details for sku " + sku + " are not available. This method requires to be run with the app in foreground.", - null); + "ACTIVITY_UNAVAILABLE", + "Details for sku " + + sku + + " are not available. This method requires to be run with the app in foreground.", + null); return; } 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 34ce31943542..41a60788691f 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 @@ -29,6 +29,7 @@ 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; @@ -66,25 +67,13 @@ public class InAppPurchasePluginTest { @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); - } - - @Test - public void doNotRegisterWithoutActivityAvailable() { - InAppPurchasePlugin.registerWith(registrar); - verify(registrar, never()).messenger(); - } - - @Test - public void registerWithoutActivityAvailable() { - when(registrar.messenger()).thenReturn(messenger); - when(registrar.activity()).thenReturn(activity); - InAppPurchasePlugin.registerWith(registrar); - verify(registrar).messenger(); + when(registrar.context()).thenReturn(context); + plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel); } @Test @@ -239,6 +228,7 @@ public void querySkuDetailsAsync_clientDisconnected() { @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)); @@ -265,8 +255,27 @@ public void launchBillingFlow_ok_nullAccountId() { verify(result, times(1)).success(responseCode); } + @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 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"; From e02698071b3e3107e9ff7611d01ae5a21324f293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Sat, 31 Aug 2019 01:24:55 -0300 Subject: [PATCH 08/11] removed empty line from CHANGELOG.md --- packages/in_app_purchase/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index f0f048ec3290..2f0e89e1cdec 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -2,7 +2,6 @@ * Android : Improved testability. - ## 0.2.1+2 * Android: Require a non-null Activity to use the `launchBillingFlow` method. From 739e1997772cdc4afccdb324c13b57120932547e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Sat, 31 Aug 2019 01:25:36 -0300 Subject: [PATCH 09/11] Extract BillingClientFactory to an interface to make testing simpler. --- .../plugins/inapppurchase/BillingClientFactory.java | 7 ++++++- .../flutter/plugins/inapppurchase/InAppPurchasePlugin.java | 2 +- .../plugins/inapppurchase/InAppPurchasePluginTest.java | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index e987984374fb..078986c04c86 100644 --- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -4,8 +4,13 @@ import com.android.billingclient.api.BillingClient; import io.flutter.plugin.common.MethodChannel; -public class BillingClientFactory { +interface BillingClientFactory { + BillingClient createBillingClient(Context context, MethodChannel channel); +} + +final class BillingClientFactoryImpl implements BillingClientFactory { + @Override public BillingClient createBillingClient(Context context, MethodChannel channel) { return BillingClient.newBuilder(context) .setListener(new PluginPurchaseListener(channel)) 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 cdabd1ba8f17..99f68842d1c0 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 @@ -70,7 +70,7 @@ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - final BillingClientFactory factory = new BillingClientFactory(); + final BillingClientFactory factory = new BillingClientFactoryImpl(); final InAppPurchasePlugin plugin = new InAppPurchasePlugin(factory, registrar, channel); channel.setMethodCallHandler(plugin); } 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 69a0edb78d5c..31118be8226a 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 @@ -63,13 +63,12 @@ public class InAppPurchasePluginTest { @Spy Result result; @Mock PluginRegistry.Registrar registrar; @Mock Activity activity; - @Mock BillingClientFactory factory; @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(factory.createBillingClient(any(), any())).thenReturn(mockBillingClient); + BillingClientFactory factory = (context, channel) -> mockBillingClient; plugin = new InAppPurchasePlugin(factory, registrar, mockMethodChannel); } From a07f6e26e09ca575fb1fbeac7e7cd7f12e4be670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Sat, 31 Aug 2019 02:19:07 -0300 Subject: [PATCH 10/11] fix file formatting --- packages/connectivity/ios/Classes/ConnectivityPlugin.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index c69871175b01..346e5f1777e2 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -155,7 +155,9 @@ - (NSString*)convertCLAuthorizationStatusToString:(CLAuthorizationStatus)status case kCLAuthorizationStatusAuthorizedWhenInUse: { return @"authorizedWhenInUse"; } - default: { return @"unknown"; } + default: { + return @"unknown"; + } } } From 6eabb6c72415a84e1045c8877895ac7c4bb651de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Tue, 3 Sep 2019 17:12:10 -0300 Subject: [PATCH 11/11] formatted file using clang-format-7 --- packages/connectivity/ios/Classes/ConnectivityPlugin.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index 346e5f1777e2..c69871175b01 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -155,9 +155,7 @@ - (NSString*)convertCLAuthorizationStatusToString:(CLAuthorizationStatus)status case kCLAuthorizationStatusAuthorizedWhenInUse: { return @"authorizedWhenInUse"; } - default: { - return @"unknown"; - } + default: { return @"unknown"; } } }