Skip to content

Commit

Permalink
Upgrade Flutter Android APIs (RevenueCat#74)
Browse files Browse the repository at this point in the history
* updates to new Android APIs

* fixes activity being null in old API

* adds tests

* adds pedantic

* moves function around

* missing await

* adds test_driver

* adds comment
  • Loading branch information
vegaro authored Jun 26, 2020
1 parent 858bbc1 commit da374da
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 38 deletions.
9 changes: 9 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include: package:pedantic/analysis_options.1.8.0.yaml
analyzer:
exclude:
# Ignore generated files
- '**/*.g.dart'
- 'lib/src/generated/*.dart'
linter:
rules:
- public_member_api_docs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.revenuecat.purchases_flutter;

import android.app.Activity;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

Expand All @@ -19,6 +22,10 @@
import java.util.List;
import java.util.Map;

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.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
Expand All @@ -31,18 +38,27 @@
/**
* PurchasesFlutterPlugin
*/
public class PurchasesFlutterPlugin implements MethodCallHandler {
public class PurchasesFlutterPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {

private static final String PURCHASER_INFO_UPDATED = "Purchases-PurchaserInfoUpdated";

private final Registrar registrar;
private final MethodChannel channel;
// Only set registrar for v1 embedder.
private PluginRegistry.Registrar registrar;
// Only set activity for v2 embedder. Always access activity from getActivity() method.
private Context applicationContext;
private MethodChannel channel;
private Activity activity;

private static final String PLATFORM_NAME = "flutter";
private static final String PLUGIN_VERSION = "1.2.0-SNAPSHOT";

public PurchasesFlutterPlugin(Registrar registrar, MethodChannel channel) {
this.registrar = registrar;
this.channel = channel;
/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
PurchasesFlutterPlugin instance = new PurchasesFlutterPlugin();
instance.onAttachedToEngine(registrar.messenger(), registrar.context());
instance.registrar = registrar;
registrar.addViewDestroyListener(new PluginRegistry.ViewDestroyListener() {
@Override
public boolean onViewDestroy(FlutterNativeView flutterNativeView) {
Expand All @@ -56,12 +72,51 @@ public boolean onViewDestroy(FlutterNativeView flutterNativeView) {
});
}

/**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "purchases_flutter");
channel.setMethodCallHandler(new PurchasesFlutterPlugin(registrar, channel));
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
onAttachedToEngine(binding.getBinaryMessenger(), binding.getApplicationContext());
}

private void onAttachedToEngine(BinaryMessenger messenger, Context applicationContext) {
this.channel = new MethodChannel(messenger, "purchases_flutter");
this.applicationContext = applicationContext;
this.channel.setMethodCallHandler(this);
}

@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
try {
Purchases.getSharedInstance().close();
} catch (UninitializedPropertyAccessException e) {
// there's no instance so all good
}
channel.setMethodCallHandler(null);
this.channel = null;
this.applicationContext = null;
}

@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
this.activity = binding.getActivity();
}

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

@Override
public void onDetachedFromActivity() {
this.activity = null;
}

@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}

public Activity getActivity() {
return registrar != null ? registrar.activity() : activity;
}

@Override
Expand Down Expand Up @@ -186,7 +241,7 @@ private void sendEvent(String eventName, @Nullable Map<String, Object> params) {

private void setupPurchases(String apiKey, String appUserID, @Nullable Boolean observerMode, final Result result) {
PlatformInfo platformInfo = new PlatformInfo(PLATFORM_NAME, PLUGIN_VERSION);
CommonKt.configure(this.registrar.context(), apiKey, appUserID, observerMode, platformInfo);
CommonKt.configure(this.applicationContext, apiKey, appUserID, observerMode, platformInfo);

Purchases.getSharedInstance().setUpdatedPurchaserInfoListener(new UpdatedPurchaserInfoListener() {
@Override
Expand Down Expand Up @@ -235,7 +290,7 @@ private void purchaseProduct(final String productIdentifier, final String oldSKU
@Nullable final Integer prorationMode, final String type,
final Result result) {
CommonKt.purchaseProduct(
this.registrar.activity(),
getActivity(),
productIdentifier,
oldSKU,
prorationMode,
Expand All @@ -250,7 +305,7 @@ private void purchasePackage(final String packageIdentifier,
@Nullable final Integer prorationMode,
final Result result) {
CommonKt.purchasePackage(
this.registrar.activity(),
getActivity(),
packageIdentifier,
offeringIdentifier,
oldSKU,
Expand Down
4 changes: 3 additions & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ flutter {
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
}

// Call passing parameter -PcommonPath="$HOME/Development/repos/purchases-hybrid-common/android"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.revenuecat.purchases_flutter;

import androidx.test.rule.ActivityTestRule;

import com.revenuecat.purchases_flutter_example.EmbeddingV1Activity;

import dev.flutter.plugins.e2e.FlutterRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;

@RunWith(FlutterRunner.class)
public class EmbeddingV1ActivityTest {
@Rule
public ActivityTestRule<EmbeddingV1Activity> rule =
new ActivityTestRule<>(EmbeddingV1Activity.class);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.revenuecat.purchases_flutter;

import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.e2e.FlutterRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;
import io.flutter.embedding.android.FlutterActivity;

@RunWith(FlutterRunner.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
}
13 changes: 10 additions & 3 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
android:label="purchases_flutter_example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
Expand All @@ -29,5 +28,13 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".EmbeddingV1Activity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
<meta-data android:name="flutterEmbedding" android:value="2"/>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.revenuecat.purchases_flutter_example;

import android.os.Bundle;
import com.revenuecat.purchases_flutter.PurchasesFlutterPlugin;
import io.flutter.app.FlutterActivity;
import io.flutter.view.FlutterMain;

public class EmbeddingV1Activity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
FlutterMain.startInitialization(this);
super.onCreate(savedInstanceState);
PurchasesFlutterPlugin.registerWith(registrarFor("com.revenuecat.purchases_flutter"));
}
}

This file was deleted.

2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class _MyAppState extends State<InitialScreen> {
Future<void> initPlatformState() async {
await Purchases.setDebugLogsEnabled(true);
await Purchases.setup("api_key");
Purchases.addAttributionData({}, PurchasesAttributionNetwork.facebook);
await Purchases.addAttributionData({}, PurchasesAttributionNetwork.facebook);
PurchaserInfo purchaserInfo = await Purchases.getPurchaserInfo();
Offerings offerings = await Purchases.getOfferings();
// If the widget was removed from the tree while the asynchronous platform
Expand Down
5 changes: 5 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ dev_dependencies:
purchases_flutter:
path: ../

pedantic: ^1.8.0
e2e: ^0.2.1
flutter_driver:
sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec

Expand Down
15 changes: 15 additions & 0 deletions example/test_driver/purchases_flutter_e2e_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';

Future<void> main() async {
final FlutterDriver driver = await FlutterDriver.connect();
final String result =
await driver.requestData(null, timeout: const Duration(minutes: 1));
await driver.close();
exit(result == 'pass' ? 0 : 1);
}
4 changes: 2 additions & 2 deletions lib/purchases_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Purchases {
static final Set<PurchaserInfoUpdateListener> _purchaserInfoUpdateListeners =
Set();

static final _channel = new MethodChannel('purchases_flutter')
static final _channel = MethodChannel('purchases_flutter')
..setMethodCallHandler((MethodCall call) async {
switch (call.method) {
case "Purchases-PurchaserInfoUpdated":
Expand Down Expand Up @@ -143,7 +143,7 @@ class Purchases {
purchaseType = PurchaseType.inapp;
}
return purchaseProduct(productIdentifier,
upgradeInfo: new UpgradeInfo(oldSKU), type: purchaseType);
upgradeInfo: UpgradeInfo(oldSKU), type: purchaseType);
}

/// Makes a purchase. Returns a [PurchaserInfo] object. Throws a
Expand Down
6 changes: 5 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ documentation: https://docs.revenuecat.com/

environment:
sdk: ">=2.1.0 <3.0.0"
flutter: ">=1.12.0 <2.0.0"
flutter: ">=1.12.13+hotfix.6 <2.0.0"

dependencies:
flutter:
sdk: flutter

dev_dependencies:
pedantic: ^1.8.0
flutter_test:
sdk: flutter
e2e: ^0.2.1
flutter_driver:
sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
Expand Down
16 changes: 16 additions & 0 deletions test/purchases_flutter_e2e.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:e2e/e2e.dart';
import 'package:purchases_flutter/purchases_flutter.dart';

// Execute the following in the `example` folder run these tests:
// `flutter drive --driver=test_driver/purchases_flutter_e2e_test.dart ../test/purchases_flutter_e2e.dart`

void main() {
E2EWidgetsFlutterBinding.ensureInitialized();

testWidgets('Can setup Purchases', (WidgetTester tester) async {
final Future<void> future = Purchases.setup('apiKey', appUserId: 'cesar');
expect(future, completes);
});

}
25 changes: 23 additions & 2 deletions test/purchases_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,40 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:purchases_flutter/purchases_flutter.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

const MethodChannel channel = MethodChannel('purchases_flutter');
final List<MethodCall> log = <MethodCall>[];
dynamic response;

setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
log.add(methodCall);
return response;
});
});

tearDown(() {
channel.setMockMethodCallHandler(null);
log.clear();
response = null;
});

test('setupPurchases', () async {
Purchases.setup("api_key", appUserId: "cesar", observerMode: true);
await Purchases.setup('api_key', appUserId: 'cesar', observerMode: true);
expect(
log,
<Matcher>[
isMethodCall(
'setupPurchases',
arguments: <String, dynamic>{
'apiKey': 'api_key',
'appUserId': 'cesar',
'observerMode': true
},
)
],
);
});

}

0 comments on commit da374da

Please sign in to comment.