diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index fbbb870dcbe0..2f0e89e1cdec 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+3 + +* Android : Improved testability. + ## 0.2.1+2 * Android: Require a non-null Activity to use the `launchBillingFlow` method. 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..078986c04c86 --- /dev/null +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -0,0 +1,19 @@ +package io.flutter.plugins.inapppurchase; + +import android.content.Context; +import com.android.billingclient.api.BillingClient; +import io.flutter.plugin.common.MethodChannel; + +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)) + .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 e914d2a455de..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 @@ -19,7 +19,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,6 +36,7 @@ public class InAppPurchasePlugin implements MethodCallHandler { private static final String TAG = "InAppPurchasePlugin"; private @Nullable BillingClient billingClient; + private final BillingClientFactory factory; private final Registrar registrar; private final Context applicationContext; private final MethodChannel channel; @@ -69,12 +69,17 @@ static final class MethodNames { public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - channel.setMethodCallHandler(new InAppPurchasePlugin(registrar, channel)); + + final BillingClientFactory factory = new BillingClientFactoryImpl(); + final InAppPurchasePlugin plugin = new InAppPurchasePlugin(factory, registrar, channel); + channel.setMethodCallHandler(plugin); } - public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) { + public InAppPurchasePlugin( + BillingClientFactory factory, Registrar registrar, MethodChannel channel) { this.applicationContext = registrar.context(); this.registrar = registrar; + this.factory = factory; this.channel = channel; } @@ -112,18 +117,9 @@ public void onMethodCall(MethodCall call, Result result) { } } - @VisibleForTesting - /*package*/ InAppPurchasePlugin( - Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) { - this.billingClient = billingClient; - this.channel = channel; - this.applicationContext = registrar.context(); - this.registrar = registrar; - } - private void startConnection(final int handle, final Result result) { if (billingClient == null) { - billingClient = buildBillingClient(applicationContext, channel); + billingClient = factory.createBillingClient(applicationContext, channel); } billingClient.startConnection( @@ -282,27 +278,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 41a60788691f..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 @@ -29,7 +29,6 @@ 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; @@ -43,12 +42,10 @@ 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; import java.util.Map; @@ -60,20 +57,19 @@ import org.mockito.Spy; public class InAppPurchasePluginTest { - InAppPurchasePlugin plugin; + private InAppPurchasePlugin plugin; @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); - when(registrar.context()).thenReturn(context); - plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel); + + BillingClientFactory factory = (context, channel) -> mockBillingClient; + plugin = new InAppPurchasePlugin(factory, registrar, mockMethodChannel); } @Test @@ -85,6 +81,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); @@ -93,6 +90,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); @@ -113,14 +111,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); @@ -452,6 +443,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) { diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index d3a8cf0fa230..e8e9ceea8fc9 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+2 +version: 0.2.1+3 dependencies: