Skip to content

Commit ffb93b8

Browse files
mvanbeusekomfotiDim
authored andcommitted
[in_app_purchase] Android example using in_app_purchase_android package (flutter#3861)
Adds the example app for the in_app_purchase_android platform implementation. Adds the Android implementation for issue flutter/flutter#81695 NOTE: this PR builds on top of the "[in_app_purchase] Federated Android implementation" pull request. If have split in into a separate PR so it would be easier to review.
1 parent 8e0a590 commit ffb93b8

File tree

28 files changed

+928
-2
lines changed

28 files changed

+928
-2
lines changed

packages/in_app_purchase/in_app_purchase/example/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ below.
3232
- `subscription_silver`: A lower level subscription.
3333
- `subscription_gold`: A higher level subscription.
3434

35-
Make sure that all of the products are set to `ACTIVE`.
35+
Make sure that all the products are set to `ACTIVE`.
3636

3737
4. Update `APP_ID` in `example/android/app/build.gradle` to match your package
3838
ID in the PDC.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# In App Purchase Example
2+
3+
Demonstrates how to use the In App Purchase Android (IAP) Plugin.
4+
5+
## Getting Started
6+
7+
### Preparation
8+
9+
There's a significant amount of setup required for testing in-app purchases
10+
successfully, including registering new app IDs and store entries to use for
11+
testing in the Play Developer Console. Google Play requires developers to
12+
configure an app with in-app items for purchase to call their in-app-purchase
13+
APIs. The Google Play Store has extensive documentation on how to do this, and
14+
we've also included a high level guide below.
15+
16+
* [Google Play Billing Overview](https://developer.android.com/google/play/billing/billing_overview)
17+
18+
### Android
19+
20+
1. Create a new app in the [Play Developer
21+
Console](https://play.google.com/apps/publish/) (PDC).
22+
23+
2. Sign up for a merchant's account in the PDC.
24+
25+
3. Create IAPs in the PDC available for purchase in the app. The example assumes
26+
the following SKU IDs exist:
27+
28+
- `consumable`: A managed product.
29+
- `upgrade`: A managed product.
30+
- `subscription_silver`: A lower level subscription.
31+
- `subscription_gold`: A higher level subscription.
32+
33+
Make sure that all of the products are set to `ACTIVE`.
34+
35+
4. Update `APP_ID` in `example/android/app/build.gradle` to match your package
36+
ID in the PDC.
37+
38+
5. Create an `example/android/keystore.properties` file with all your signing
39+
information. `keystore.example.properties` exists as an example to follow.
40+
It's impossible to use any of the `BillingClient` APIs from an unsigned APK.
41+
See
42+
[here](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore)
43+
and [here](https://developer.android.com/studio/publish/app-signing#sign-apk)
44+
for more information.
45+
46+
6. Build a signed apk. `flutter build apk` will work for this, the gradle files
47+
in this project have been configured to sign even debug builds.
48+
49+
7. Upload the signed APK from step 6 to the PDC, and publish that to the alpha
50+
test channel. Add your test account as an approved tester. The
51+
`BillingClient` APIs won't work unless the app has been fully published to
52+
the alpha channel and is being used by an authorized test account. See
53+
[here](https://support.google.com/googleplay/android-developer/answer/3131213)
54+
for more info.
55+
56+
8. Sign in to the test device with the test account from step #7. Then use
57+
`flutter run` to install the app to the device and test like normal.
58+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
def localProperties = new Properties()
2+
def localPropertiesFile = rootProject.file('local.properties')
3+
if (localPropertiesFile.exists()) {
4+
localPropertiesFile.withReader('UTF-8') { reader ->
5+
localProperties.load(reader)
6+
}
7+
}
8+
9+
// Load the build signing secrets from a local `keystore.properties` file.
10+
// TODO(YOU): Create release keys and a `keystore.properties` file. See
11+
// `example/README.md` for more info and `keystore.example.properties` for an
12+
// example.
13+
def keystorePropertiesFile = rootProject.file("keystore.properties")
14+
def keystoreProperties = new Properties()
15+
def configured = true
16+
try {
17+
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
18+
} catch (IOException e) {
19+
configured = false
20+
logger.error('Release signing information not found.')
21+
}
22+
23+
project.ext {
24+
// TODO(YOU): Create release keys and a `keystore.properties` file. See
25+
// `example/README.md` for more info and `keystore.example.properties` for an
26+
// example.
27+
APP_ID = configured ? keystoreProperties['appId'] : "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE"
28+
KEYSTORE_STORE_FILE = configured ? rootProject.file(keystoreProperties['storeFile']) : null
29+
KEYSTORE_STORE_PASSWORD = keystoreProperties['storePassword']
30+
KEYSTORE_KEY_ALIAS = keystoreProperties['keyAlias']
31+
KEYSTORE_KEY_PASSWORD = keystoreProperties['keyPassword']
32+
VERSION_CODE = configured ? keystoreProperties['versionCode'].toInteger() : 1
33+
VERSION_NAME = configured ? keystoreProperties['versionName'] : "0.0.1"
34+
}
35+
36+
if (project.APP_ID == "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE") {
37+
configured = false
38+
logger.error('Unique package name not set, defaulting to "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE".')
39+
}
40+
41+
// Log a final error message if we're unable to create a release key signed
42+
// build for an app configured in the Play Developer Console. Apks built in this
43+
// condition won't be able to call any of the BillingClient APIs.
44+
if (!configured) {
45+
logger.error('The app could not be configured for release signing. In app purchases will not be testable. See `example/README.md` for more info and instructions.')
46+
}
47+
48+
def flutterRoot = localProperties.getProperty('flutter.sdk')
49+
if (flutterRoot == null) {
50+
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
51+
}
52+
53+
apply plugin: 'com.android.application'
54+
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
55+
56+
android {
57+
signingConfigs {
58+
release {
59+
storeFile project.KEYSTORE_STORE_FILE
60+
storePassword project.KEYSTORE_STORE_PASSWORD
61+
keyAlias project.KEYSTORE_KEY_ALIAS
62+
keyPassword project.KEYSTORE_KEY_PASSWORD
63+
}
64+
}
65+
66+
compileSdkVersion 29
67+
68+
lintOptions {
69+
disable 'InvalidPackage'
70+
}
71+
72+
defaultConfig {
73+
applicationId project.APP_ID
74+
minSdkVersion 16
75+
targetSdkVersion 29
76+
versionCode project.VERSION_CODE
77+
versionName project.VERSION_NAME
78+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
79+
}
80+
81+
buildTypes {
82+
// Google Play Billing APIs only work with apps signed for production.
83+
debug {
84+
if (configured) {
85+
signingConfig signingConfigs.release
86+
} else {
87+
signingConfig signingConfigs.debug
88+
}
89+
}
90+
release {
91+
if (configured) {
92+
signingConfig signingConfigs.release
93+
} else {
94+
signingConfig signingConfigs.debug
95+
}
96+
}
97+
}
98+
99+
testOptions {
100+
unitTests.returnDefaultValues = true
101+
}
102+
}
103+
104+
flutter {
105+
source '../..'
106+
}
107+
108+
dependencies {
109+
implementation 'com.android.billingclient:billing:3.0.2'
110+
testImplementation 'junit:junit:4.12'
111+
testImplementation 'org.mockito:mockito-core:3.6.0'
112+
testImplementation 'org.json:json:20180813'
113+
androidTestImplementation 'androidx.test:runner:1.1.1'
114+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="io.flutter.plugins.inapppurchaseexample">
3+
4+
<!-- The INTERNET permission is required for development. Specifically,
5+
flutter needs it to communicate with the running application
6+
to allow setting breakpoints, to provide hot reload, etc.
7+
-->
8+
<uses-permission android:name="android.permission.INTERNET"/>
9+
10+
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
11+
calls FlutterMain.startInitialization(this); in its onCreate method.
12+
In most cases you can leave this as-is, but you if you want to provide
13+
additional functionality it is fine to subclass or reimplement
14+
FlutterApplication and put your custom class here. -->
15+
<application
16+
android:name="io.flutter.app.FlutterApplication"
17+
android:label="in_app_purchase_example"
18+
android:icon="@mipmap/ic_launcher">
19+
<activity
20+
android:name=".EmbeddingV1Activity"
21+
android:launchMode="singleTop"
22+
android:theme="@style/LaunchTheme"
23+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
24+
android:hardwareAccelerated="true"
25+
android:exported="true"
26+
android:windowSoftInputMode="adjustResize">
27+
<!-- This keeps the window background of the activity showing
28+
until Flutter renders its first frame. It can be removed if
29+
there is no splash screen (such as the default splash screen
30+
defined in @style/LaunchTheme). -->
31+
<meta-data
32+
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
33+
android:value="true" />
34+
</activity>
35+
<activity
36+
android:name="io.flutter.embedding.android.FlutterActivity"
37+
android:theme="@style/LaunchTheme"
38+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
39+
android:hardwareAccelerated="true"
40+
android:windowSoftInputMode="adjustResize">
41+
<intent-filter>
42+
<action android:name="android.intent.action.MAIN"/>
43+
<category android:name="android.intent.category.LAUNCHER"/>
44+
</intent-filter>
45+
</activity>
46+
<meta-data android:name="flutterEmbedding" android:value="2"/>
47+
</application>
48+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.inapppurchaseexample;
6+
7+
import android.os.Bundle;
8+
import dev.flutter.plugins.integration_test.IntegrationTestPlugin;
9+
import io.flutter.plugins.inapppurchase.InAppPurchasePlugin;
10+
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
11+
12+
@SuppressWarnings("deprecation")
13+
public class EmbeddingV1Activity extends io.flutter.app.FlutterActivity {
14+
@Override
15+
protected void onCreate(Bundle savedInstanceState) {
16+
super.onCreate(savedInstanceState);
17+
IntegrationTestPlugin.registerWith(
18+
registrarFor("dev.flutter.plugins.integration_test.IntegrationTestPlugin"));
19+
SharedPreferencesPlugin.registerWith(
20+
registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
21+
InAppPurchasePlugin.registerWith(
22+
registrarFor("io.flutter.plugins.inapppurchase.InAppPurchasePlugin"));
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.inapppurchaseexample;
6+
7+
import androidx.test.rule.ActivityTestRule;
8+
import dev.flutter.plugins.integration_test.FlutterTestRunner;
9+
import org.junit.Rule;
10+
import org.junit.runner.RunWith;
11+
12+
@RunWith(FlutterTestRunner.class)
13+
@SuppressWarnings("deprecation")
14+
public class EmbeddingV1ActivityTest {
15+
@Rule
16+
public ActivityTestRule<EmbeddingV1Activity> rule =
17+
new ActivityTestRule<>(EmbeddingV1Activity.class);
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.inapppurchaseexample;
6+
7+
import androidx.test.rule.ActivityTestRule;
8+
import dev.flutter.plugins.integration_test.FlutterTestRunner;
9+
import io.flutter.embedding.android.FlutterActivity;
10+
import org.junit.Rule;
11+
import org.junit.runner.RunWith;
12+
13+
@RunWith(FlutterTestRunner.class)
14+
public class FlutterActivityTest {
15+
@Rule
16+
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Modify this file to customize your launch splash screen -->
3+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:drawable="@android:color/white" />
5+
6+
<!-- You can insert your own image assets here -->
7+
<!-- <item>
8+
<bitmap
9+
android:gravity="center"
10+
android:src="@mipmap/launch_image" />
11+
</item> -->
12+
</layer-list>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
4+
<!-- Show a splash screen on the activity. Automatically removed when
5+
Flutter draws its first frame -->
6+
<item name="android:windowBackground">@drawable/launch_background</item>
7+
</style>
8+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
buildscript {
2+
repositories {
3+
google()
4+
jcenter()
5+
}
6+
7+
dependencies {
8+
classpath 'com.android.tools.build:gradle:3.3.0'
9+
}
10+
}
11+
12+
allprojects {
13+
repositories {
14+
google()
15+
jcenter()
16+
}
17+
}
18+
19+
rootProject.buildDir = '../build'
20+
subprojects {
21+
project.buildDir = "${rootProject.buildDir}/${project.name}"
22+
}
23+
subprojects {
24+
project.evaluationDependsOn(':app')
25+
}
26+
27+
task clean(type: Delete) {
28+
delete rootProject.buildDir
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
org.gradle.jvmargs=-Xmx1536M
2+
android.enableR8=true
3+
android.useAndroidX=true
4+
android.enableJetifier=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#Fri Jun 23 08:50:38 CEST 2017
2+
distributionBase=GRADLE_USER_HOME
3+
distributionPath=wrapper/dists
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
storePassword=???
2+
keyPassword=???
3+
keyAlias=???
4+
storeFile=???
5+
appId=io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE
6+
versionCode=1
7+
versionName=0.0.1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
include ':app'
2+
3+
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4+
5+
def plugins = new Properties()
6+
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7+
if (pluginsFile.exists()) {
8+
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9+
}
10+
11+
plugins.each { name, path ->
12+
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13+
include ":$name"
14+
project(":$name").projectDir = pluginDirectory
15+
}

0 commit comments

Comments
 (0)