Skip to content

Commit

Permalink
[in_app_purchase] Federated Android implementation (flutter#3841)
Browse files Browse the repository at this point in the history
* Implement BillingClientWrapper and unit-tests

* Android specific implementation

* Moved Android specific methods into addition class

* Added missing line end

* Purchases status to restored after call restorePurchases

* Don't force GooglePlayPurchaseParam

* Implement registerPlatform method

* Added TODO comment to add example

* Fixed mistake in API documentation

* Added additional assert to test enablePendingPurchase

* Update Android implementation with latest platform_interface
  • Loading branch information
mvanbeusekom committed May 11, 2021
1 parent f06a2b4 commit 1a4cee7
Show file tree
Hide file tree
Showing 46 changed files with 5,591 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0

* Initial open-source release.
25 changes: 25 additions & 0 deletions packages/in_app_purchase/in_app_purchase_android/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 changes: 38 additions & 0 deletions packages/in_app_purchase/in_app_purchase_android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# in_app_purchase_android

The Android implementation of [`in_app_purchase`][1].

## Usage

### Import the package

This package has been endorsed, meaning that you only need to add `in_app_purchase`
as a dependency in your `pubspec.yaml`. It will be automatically included in your app
when you depend on `package:in_app_purchase`.

This is what the above means to your `pubspec.yaml`:

```yaml
...
dependencies:
...
in_app_purchase: ^0.6.0
...
```

If you wish to use the Android package only, you can add `in_app_purchase_android` as a
dependency:

```yaml
...
dependencies:
...
in_app_purchase_android: ^1.0.0
...
```

## TODO
- [ ] Add an example application demonstrating the use of the [in_app_purchase_android] package (see also issue [flutter/flutter#81695](https://github.com/flutter/flutter/issues/81695)).


[1]: ../in_app_purchase/in_app_purchase
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: ../../../analysis_options_legacy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
group 'io.flutter.plugins.inapppurchase'
version '1.0-SNAPSHOT'

buildscript {
repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
}
}

rootProject.allprojects {
repositories {
google()
jcenter()
}
}

apply plugin: 'com.android.library'

android {
compileSdkVersion 29

defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'com.android.billingclient:billing:3.0.2'
testImplementation 'junit:junit:4.12'
testImplementation 'org.json:json:20180813'
testImplementation 'org.mockito:mockito-core:3.6.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536M
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Mon Oct 29 10:30:44 PDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'in_app_purchase'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.inapppurchase">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.inapppurchase;

import android.content.Context;
import androidx.annotation.NonNull;
import com.android.billingclient.api.BillingClient;
import io.flutter.plugin.common.MethodChannel;

/** Responsible for creating a {@link BillingClient} object. */
interface BillingClientFactory {

/**
* Creates and returns a {@link BillingClient}.
*
* @param context The context used to create the {@link BillingClient}.
* @param channel The method channel used to create the {@link BillingClient}.
* @param enablePendingPurchases Whether to enable pending purchases. Throws an exception if it is
* false.
* @return The {@link BillingClient} object that is created.
*/
BillingClient createBillingClient(
@NonNull Context context, @NonNull MethodChannel channel, boolean enablePendingPurchases);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.inapppurchase;

import android.content.Context;
import com.android.billingclient.api.BillingClient;
import io.flutter.plugin.common.MethodChannel;

/** The implementation for {@link BillingClientFactory} for the plugin. */
final class BillingClientFactoryImpl implements BillingClientFactory {

@Override
public BillingClient createBillingClient(
Context context, MethodChannel channel, boolean enablePendingPurchases) {
BillingClient.Builder builder = BillingClient.newBuilder(context);
if (enablePendingPurchases) {
builder.enablePendingPurchases();
}
return builder.setListener(new PluginPurchaseListener(channel)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;

/** Wraps a {@link BillingClient} instance and responds to Dart calls for it. */
public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware {

@VisibleForTesting
static final class MethodNames {
static final String IS_READY = "BillingClient#isReady()";
static final String START_CONNECTION =
"BillingClient#startConnection(BillingClientStateListener)";
static final String END_CONNECTION = "BillingClient#endConnection()";
static final String ON_DISCONNECT = "BillingClientStateListener#onBillingServiceDisconnected()";
static final String QUERY_SKU_DETAILS =
"BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)";
static final String LAUNCH_BILLING_FLOW =
"BillingClient#launchBillingFlow(Activity, BillingFlowParams)";
static final String ON_PURCHASES_UPDATED =
"PurchasesUpdatedListener#onPurchasesUpdated(int, List<Purchase>)";
static final String QUERY_PURCHASES = "BillingClient#queryPurchases(String)";
static final String QUERY_PURCHASE_HISTORY_ASYNC =
"BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)";
static final String CONSUME_PURCHASE_ASYNC =
"BillingClient#consumeAsync(String, ConsumeResponseListener)";
static final String ACKNOWLEDGE_PURCHASE =
"BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";

private MethodNames() {};
}

private MethodChannel methodChannel;
private MethodCallHandlerImpl methodCallHandler;

/** Plugin registration. */
@SuppressWarnings("deprecation")
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
InAppPurchasePlugin plugin = new InAppPurchasePlugin();
plugin.setupMethodChannel(registrar.activity(), registrar.messenger(), registrar.context());
((Application) registrar.context().getApplicationContext())
.registerActivityLifecycleCallbacks(plugin.methodCallHandler);
}

@Override
public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) {
setupMethodChannel(
/*activity=*/ null, binding.getBinaryMessenger(), binding.getApplicationContext());
}

@Override
public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) {
teardownMethodChannel();
}

@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
methodCallHandler.setActivity(binding.getActivity());
}

@Override
public void onDetachedFromActivity() {
methodCallHandler.setActivity(null);
methodCallHandler.onDetachedFromActivity();
}

@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
onAttachedToActivity(binding);
}

@Override
public void onDetachedFromActivityForConfigChanges() {
methodCallHandler.setActivity(null);
}

private void setupMethodChannel(Activity activity, BinaryMessenger messenger, Context context) {
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/in_app_purchase");
methodCallHandler =
new MethodCallHandlerImpl(activity, context, methodChannel, new BillingClientFactoryImpl());
methodChannel.setMethodCallHandler(methodCallHandler);
}

private void teardownMethodChannel() {
methodChannel.setMethodCallHandler(null);
methodChannel = null;
methodCallHandler = null;
}

@VisibleForTesting
void setMethodCallHandler(MethodCallHandlerImpl methodCallHandler) {
this.methodCallHandler = methodCallHandler;
}
}
Loading

0 comments on commit 1a4cee7

Please sign in to comment.