[in_app_purchases] Android: make sure properly disconnect the billing client object. (#2509)
diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index d85a879..02eda42 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,6 +1,13 @@ +## 0.3.1 + +* Android: Fix a bug where the `BillingClient` is disconnected when app goes to the background. +* Android: Make sure the `BillingClient` object is disconnected before the activity is destroyed. +* Android: Fix minor compiler warning. +* Fix typo in CHANGELOG. + ## 0.3.0+3 -* Fix pendingCompletePurchase flag status to allow to complete the pruchsase. +* Fix pendingCompletePurchase flag status to allow to complete purchases. ## 0.3.0+2
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 a9302d1..b7edcfb 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
@@ -5,6 +5,7 @@ package io.flutter.plugins.inapppurchase; import android.app.Activity; +import android.app.Application; import android.content.Context; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; @@ -49,14 +50,14 @@ public static void registerWith(Registrar registrar) { InAppPurchasePlugin plugin = new InAppPurchasePlugin(); plugin.setupMethodChannel(registrar.activity(), registrar.messenger(), registrar.context()); + ((Application) registrar.context()) + .registerActivityLifecycleCallbacks(plugin.methodCallHandler); } @Override public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { setupMethodChannel( - /*activity=*/ null, - binding.getFlutterEngine().getDartExecutor(), - binding.getApplicationContext()); + /*activity=*/ null, binding.getBinaryMessenger(), binding.getApplicationContext()); } @Override @@ -72,6 +73,7 @@ @Override public void onDetachedFromActivity() { methodCallHandler.setActivity(null); + methodCallHandler.onDetachedFromActivity(); } @Override @@ -81,7 +83,7 @@ @Override public void onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity(); + methodCallHandler.setActivity(null); } private void setupMethodChannel(Activity activity, BinaryMessenger messenger, Context context) { @@ -96,4 +98,9 @@ methodChannel = null; methodCallHandler = null; } + + @VisibleForTesting + void setMethodCallHandler(MethodCallHandlerImpl methodCallHandler) { + this.methodCallHandler = methodCallHandler; + } }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index 9108ab3..335d4b8 100644 --- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
@@ -9,7 +9,9 @@ import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; import android.app.Activity; +import android.app.Application; import android.content.Context; +import android.os.Bundle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -33,7 +35,8 @@ import java.util.Map; /** Handles method channel for the plugin. */ -class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { +class MethodCallHandlerImpl + implements MethodChannel.MethodCallHandler, Application.ActivityLifecycleCallbacks { private static final String TAG = "InAppPurchasePlugin"; @@ -67,6 +70,36 @@ } @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) {} + + @Override + public void onActivityPaused(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) { + if (this.activity == activity && this.applicationContext != null) { + ((Application) this.applicationContext).unregisterActivityLifecycleCallbacks(this); + endBillingClientConnection(); + } + } + + @Override + public void onActivityStopped(Activity activity) {} + + void onDetachedFromActivity() { + endBillingClientConnection(); + } + + @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { case InAppPurchasePlugin.MethodNames.IS_READY: @@ -113,11 +146,15 @@ } private void endConnection(final MethodChannel.Result result) { + endBillingClientConnection(); + result.success(null); + } + + private void endBillingClientConnection() { if (billingClient != null) { billingClient.endConnection(); billingClient = null; } - result.success(null); } private void isReady(MethodChannel.Result result) {
diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index be00ac4..c6a9b41 100644 --- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
@@ -52,6 +52,7 @@ import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; @@ -73,6 +74,7 @@ @Spy Result result; @Mock Activity activity; @Mock Context context; + @Mock ActivityPluginBinding mockActivityPluginBinding; @Before public void setUp() { @@ -82,6 +84,7 @@ @NonNull MethodChannel channel, boolean enablePendingPurchases) -> mockBillingClient; methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory); + when(mockActivityPluginBinding.getActivity()).thenReturn(activity); } @Test @@ -551,6 +554,15 @@ verify(result, times(1)).success(fromBillingResult(billingResult)); } + @Test + public void endConnection_if_activity_dettached() { + InAppPurchasePlugin plugin = new InAppPurchasePlugin(); + plugin.setMethodCallHandler(methodChannelHandler); + mockStartConnection(); + plugin.onDetachedFromActivity(); + verify(mockBillingClient).endConnection(); + } + private ArgumentCaptor<BillingClientStateListener> mockStartConnection() { Map<String, Object> arguments = new HashMap<>(); arguments.put("handle", 1);
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart index f2cd87b..581a7bd 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
@@ -161,19 +161,6 @@ 'The method <refreshPurchaseVerificationData> only works on iOS.'); } - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.paused: - _disconnect(); - break; - case AppLifecycleState.resumed: - _readyFuture = _connect(); - break; - default: - } - } - @visibleForTesting static void reset() => _instance = null; @@ -189,8 +176,6 @@ Future<void> _connect() => billingClient.startConnection(onBillingServiceDisconnected: () {}); - Future<void> _disconnect() => billingClient.endConnection(); - /// Query the product detail list. /// /// This method only returns [ProductDetailsResponse].
diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart index dfad32d..f06c4ff 100644 --- a/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart +++ b/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart
@@ -56,18 +56,6 @@ test('connects on initialization', () { expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); - - test('disconnects on app pause', () { - expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(0)); - connection.didChangeAppLifecycleState(AppLifecycleState.paused); - expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(1)); - }); - - test('reconnects on app resume', () { - expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); - connection.didChangeAppLifecycleState(AppLifecycleState.resumed); - expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); - }); }); group('isAvailable', () {